summaryrefslogtreecommitdiffstats
path: root/drivers/net/wan
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wan')
-rw-r--r--drivers/net/wan/Kconfig607
-rw-r--r--drivers/net/wan/Makefile86
-rw-r--r--drivers/net/wan/c101.c446
-rw-r--r--drivers/net/wan/cosa.c2100
-rw-r--r--drivers/net/wan/cosa.h117
-rw-r--r--drivers/net/wan/cycx_drv.c586
-rw-r--r--drivers/net/wan/cycx_main.c351
-rw-r--r--drivers/net/wan/cycx_x25.c1609
-rw-r--r--drivers/net/wan/dlci.c566
-rw-r--r--drivers/net/wan/dscc4.c2074
-rw-r--r--drivers/net/wan/farsync.c2712
-rw-r--r--drivers/net/wan/farsync.h357
-rw-r--r--drivers/net/wan/hd64570.h241
-rw-r--r--drivers/net/wan/hd64572.h527
-rw-r--r--drivers/net/wan/hd6457x.c853
-rw-r--r--drivers/net/wan/hdlc_cisco.c330
-rw-r--r--drivers/net/wan/hdlc_fr.c1237
-rw-r--r--drivers/net/wan/hdlc_generic.c343
-rw-r--r--drivers/net/wan/hdlc_ppp.c115
-rw-r--r--drivers/net/wan/hdlc_raw.c90
-rw-r--r--drivers/net/wan/hdlc_raw_eth.c107
-rw-r--r--drivers/net/wan/hdlc_x25.c219
-rw-r--r--drivers/net/wan/hostess_sv11.c420
-rw-r--r--drivers/net/wan/lapbether.c465
-rw-r--r--drivers/net/wan/lmc/Makefile17
-rw-r--r--drivers/net/wan/lmc/lmc.h33
-rw-r--r--drivers/net/wan/lmc/lmc_debug.c85
-rw-r--r--drivers/net/wan/lmc/lmc_debug.h52
-rw-r--r--drivers/net/wan/lmc/lmc_ioctl.h257
-rw-r--r--drivers/net/wan/lmc/lmc_main.c2201
-rw-r--r--drivers/net/wan/lmc/lmc_media.c1246
-rw-r--r--drivers/net/wan/lmc/lmc_media.h65
-rw-r--r--drivers/net/wan/lmc/lmc_prot.h15
-rw-r--r--drivers/net/wan/lmc/lmc_proto.c249
-rw-r--r--drivers/net/wan/lmc/lmc_proto.h16
-rw-r--r--drivers/net/wan/lmc/lmc_var.h570
-rw-r--r--drivers/net/wan/n2.c562
-rw-r--r--drivers/net/wan/pc300-falc-lh.h1238
-rw-r--r--drivers/net/wan/pc300.h497
-rw-r--r--drivers/net/wan/pc300_drv.c3692
-rw-r--r--drivers/net/wan/pc300_tty.c1095
-rw-r--r--drivers/net/wan/pci200syn.c488
-rw-r--r--drivers/net/wan/sbni.c1735
-rw-r--r--drivers/net/wan/sbni.h141
-rw-r--r--drivers/net/wan/sdla.c1676
-rw-r--r--drivers/net/wan/sdla_chdlc.c4433
-rw-r--r--drivers/net/wan/sdla_fr.c5068
-rw-r--r--drivers/net/wan/sdla_ft1.c344
-rw-r--r--drivers/net/wan/sdla_ppp.c3429
-rw-r--r--drivers/net/wan/sdla_x25.c5496
-rw-r--r--drivers/net/wan/sdladrv.c2318
-rw-r--r--drivers/net/wan/sdlamain.c1341
-rw-r--r--drivers/net/wan/sealevel.c469
-rw-r--r--drivers/net/wan/syncppp.c1488
-rw-r--r--drivers/net/wan/wanpipe_multppp.c2357
-rw-r--r--drivers/net/wan/wanxl.c839
-rw-r--r--drivers/net/wan/wanxl.h152
-rw-r--r--drivers/net/wan/wanxlfw.S895
-rw-r--r--drivers/net/wan/wanxlfw.inc_shipped158
-rw-r--r--drivers/net/wan/x25_asy.c844
-rw-r--r--drivers/net/wan/x25_asy.h50
-rw-r--r--drivers/net/wan/z85230.c1851
-rw-r--r--drivers/net/wan/z85230.h449
63 files changed, 64469 insertions, 0 deletions
diff --git a/drivers/net/wan/Kconfig b/drivers/net/wan/Kconfig
new file mode 100644
index 00000000000..35791934a60
--- /dev/null
+++ b/drivers/net/wan/Kconfig
@@ -0,0 +1,607 @@
+#
+# wan devices configuration
+#
+
+menu "Wan interfaces"
+ depends on NETDEVICES
+
+config WAN
+ bool "Wan interfaces support"
+ ---help---
+ Wide Area Networks (WANs), such as X.25, Frame Relay and leased
+ lines, are used to interconnect Local Area Networks (LANs) over vast
+ distances with data transfer rates significantly higher than those
+ achievable with commonly used asynchronous modem connections.
+
+ Usually, a quite expensive external device called a `WAN router' is
+ needed to connect to a WAN. As an alternative, a relatively
+ inexpensive WAN interface card can allow your Linux box to directly
+ connect to a WAN.
+
+ If you have one of those cards and wish to use it under Linux,
+ say Y here and also to the WAN driver for your card.
+
+ If unsure, say N.
+
+# There is no way to detect a comtrol sv11 - force it modular for now.
+config HOSTESS_SV11
+ tristate "Comtrol Hostess SV-11 support"
+ depends on WAN && ISA && m
+ help
+ Driver for Comtrol Hostess SV-11 network card which
+ operates on low speed synchronous serial links at up to
+ 256Kbps, supporting PPP and Cisco HDLC.
+
+ The driver will be compiled as a module: the
+ module will be called hostess_sv11.
+
+# The COSA/SRP driver has not been tested as non-modular yet.
+config COSA
+ tristate "COSA/SRP sync serial boards support"
+ depends on WAN && ISA && m
+ ---help---
+ Driver for COSA and SRP synchronous serial boards.
+
+ These boards allow to connect synchronous serial devices (for example
+ base-band modems, or any other device with the X.21, V.24, V.35 or
+ V.36 interface) to your Linux box. The cards can work as the
+ character device, synchronous PPP network device, or the Cisco HDLC
+ network device.
+
+ You will need user-space utilities COSA or SRP boards for downloading
+ the firmware to the cards and to set them up. Look at the
+ <http://www.fi.muni.cz/~kas/cosa/> for more information. You can also
+ read the comment at the top of the <file:drivers/net/wan/cosa.c> for
+ details about the cards and the driver itself.
+
+ The driver will be compiled as a module: the
+ module will be called cosa.
+
+config DSCC4
+ tristate "Etinc PCISYNC serial board support"
+ depends on WAN && PCI && m
+ help
+ Driver for Etinc PCISYNC boards based on the Infineon (ex. Siemens)
+ DSCC4 chipset.
+
+ This is supposed to work with the four port card. Take a look at
+ <http://www.cogenit.fr/dscc4/> for further information about the
+ driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dscc4.
+
+config DSCC4_PCISYNC
+ bool "Etinc PCISYNC features"
+ depends on DSCC4
+ help
+ Due to Etinc's design choice for its PCISYNC cards, some operations
+ are only allowed on specific ports of the DSCC4. This option is the
+ only way for the driver to know that it shouldn't return a success
+ code for these operations.
+
+ Please say Y if your card is an Etinc's PCISYNC.
+
+config DSCC4_PCI_RST
+ bool "Hard reset support"
+ depends on DSCC4
+ help
+ Various DSCC4 bugs forbid any reliable software reset of the ASIC.
+ As a replacement, some vendors provide a way to assert the PCI #RST
+ pin of DSCC4 through the GPIO port of the card. If you choose Y,
+ the driver will make use of this feature before module removal
+ (i.e. rmmod). The feature is known to be available on Commtech's
+ cards. Contact your manufacturer for details.
+
+ Say Y if your card supports this feature.
+
+#
+# Lan Media's board. Currently 1000, 1200, 5200, 5245
+#
+config LANMEDIA
+ tristate "LanMedia Corp. SSI/V.35, T1/E1, HSSI, T3 boards"
+ depends on WAN && PCI
+ ---help---
+ Driver for the following Lan Media family of serial boards:
+
+ - LMC 1000 board allows you to connect synchronous serial devices
+ (for example base-band modems, or any other device with the X.21,
+ V.24, V.35 or V.36 interface) to your Linux box.
+
+ - LMC 1200 with on board DSU board allows you to connect your Linux
+ box directly to a T1 or E1 circuit.
+
+ - LMC 5200 board provides a HSSI interface capable of running up to
+ 52 Mbits per second.
+
+ - LMC 5245 board connects directly to a T3 circuit saving the
+ additional external hardware.
+
+ To change setting such as syncPPP vs Cisco HDLC or clock source you
+ will need lmcctl. It is available at <ftp://ftp.lanmedia.com/>
+ (broken link).
+
+ To compile this driver as a module, choose M here: the
+ module will be called lmc.
+
+# There is no way to detect a Sealevel board. Force it modular
+config SEALEVEL_4021
+ tristate "Sealevel Systems 4021 support"
+ depends on WAN && ISA && m
+ help
+ This is a driver for the Sealevel Systems ACB 56 serial I/O adapter.
+
+ The driver will be compiled as a module: the
+ module will be called sealevel.
+
+config SYNCLINK_SYNCPPP
+ tristate "SyncLink HDLC/SYNCPPP support"
+ depends on WAN
+ help
+ Enables HDLC/SYNCPPP support for the SyncLink WAN driver.
+
+ Normally the SyncLink WAN driver works with the main PPP driver
+ <file:drivers/net/ppp_generic.c> and pppd program.
+ HDLC/SYNCPPP support allows use of the Cisco HDLC/PPP driver
+ <file:drivers/net/wan/syncppp.c>. The SyncLink WAN driver (in
+ character devices) must also be enabled.
+
+# Generic HDLC
+config HDLC
+ tristate "Generic HDLC layer"
+ depends on WAN
+ help
+ Say Y to this option if your Linux box contains a WAN (Wide Area
+ Network) card supported by this driver and you are planning to
+ connect the box to a WAN.
+
+ You will need supporting software from
+ <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+ Generic HDLC driver currently supports raw HDLC, Cisco HDLC, Frame
+ Relay, synchronous Point-to-Point Protocol (PPP) and X.25.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hdlc.
+
+ If unsure, say N.
+
+config HDLC_RAW
+ bool "Raw HDLC support"
+ depends on HDLC
+ help
+ Generic HDLC driver supporting raw HDLC over WAN connections.
+
+ If unsure, say N.
+
+config HDLC_RAW_ETH
+ bool "Raw HDLC Ethernet device support"
+ depends on HDLC
+ help
+ Generic HDLC driver supporting raw HDLC Ethernet device emulation
+ over WAN connections.
+
+ You will need it for Ethernet over HDLC bridges.
+
+ If unsure, say N.
+
+config HDLC_CISCO
+ bool "Cisco HDLC support"
+ depends on HDLC
+ help
+ Generic HDLC driver supporting Cisco HDLC over WAN connections.
+
+ If unsure, say N.
+
+config HDLC_FR
+ bool "Frame Relay support"
+ depends on HDLC
+ help
+ Generic HDLC driver supporting Frame Relay over WAN connections.
+
+ If unsure, say N.
+
+config HDLC_PPP
+ bool "Synchronous Point-to-Point Protocol (PPP) support"
+ depends on HDLC
+ help
+ Generic HDLC driver supporting PPP over WAN connections.
+
+ If unsure, say N.
+
+config HDLC_X25
+ bool "X.25 protocol support"
+ depends on HDLC && (LAPB=m && HDLC=m || LAPB=y)
+ help
+ Generic HDLC driver supporting X.25 over WAN connections.
+
+ If unsure, say N.
+
+comment "X.25/LAPB support is disabled"
+ depends on WAN && HDLC && (LAPB!=m || HDLC!=m) && LAPB!=y
+
+config PCI200SYN
+ tristate "Goramo PCI200SYN support"
+ depends on HDLC && PCI
+ help
+ Driver for PCI200SYN cards by Goramo sp. j.
+
+ If you have such a card, say Y here and see
+ <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+ To compile this as a module, choose M here: the
+ module will be called pci200syn.
+
+ If unsure, say N.
+
+config WANXL
+ tristate "SBE Inc. wanXL support"
+ depends on HDLC && PCI
+ help
+ Driver for wanXL PCI cards by SBE Inc.
+
+ If you have such a card, say Y here and see
+ <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+ To compile this as a module, choose M here: the
+ module will be called wanxl.
+
+ If unsure, say N.
+
+config WANXL_BUILD_FIRMWARE
+ bool "rebuild wanXL firmware"
+ depends on WANXL && !PREVENT_FIRMWARE_BUILD
+ help
+ Allows you to rebuild firmware run by the QUICC processor.
+ It requires as68k, ld68k and hexdump programs.
+
+ You should never need this option, say N.
+
+config PC300
+ tristate "Cyclades-PC300 support (RS-232/V.35, X.21, T1/E1 boards)"
+ depends on HDLC && PCI
+ ---help---
+ Driver for the Cyclades-PC300 synchronous communication boards.
+
+ These boards provide synchronous serial interfaces to your
+ Linux box (interfaces currently available are RS-232/V.35, X.21 and
+ T1/E1). If you wish to support Multilink PPP, please select the
+ option later and read the file README.mlppp provided by PC300
+ package.
+
+ To compile this as a module, choose M here: the module
+ will be called pc300.
+
+ If unsure, say N.
+
+config PC300_MLPPP
+ bool "Cyclades-PC300 MLPPP support"
+ depends on PC300 && PPP_MULTILINK && PPP_SYNC_TTY && HDLC_PPP
+ help
+ Multilink PPP over the PC300 synchronous communication boards.
+
+comment "Cyclades-PC300 MLPPP support is disabled."
+ depends on WAN && HDLC && PC300 && (PPP=n || !PPP_MULTILINK || PPP_SYNC_TTY=n || !HDLC_PPP)
+
+comment "Refer to the file README.mlppp, provided by PC300 package."
+ depends on WAN && HDLC && PC300 && (PPP=n || !PPP_MULTILINK || PPP_SYNC_TTY=n || !HDLC_PPP)
+
+config N2
+ tristate "SDL RISCom/N2 support"
+ depends on HDLC && ISA
+ help
+ Driver for RISCom/N2 single or dual channel ISA cards by
+ SDL Communications Inc.
+
+ If you have such a card, say Y here and see
+ <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+ Note that N2csu and N2dds cards are not supported by this driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called n2.
+
+ If unsure, say N.
+
+config C101
+ tristate "Moxa C101 support"
+ depends on HDLC && ISA
+ help
+ Driver for C101 SuperSync ISA cards by Moxa Technologies Co., Ltd.
+
+ If you have such a card, say Y here and see
+ <http://www.kernel.org/pub/linux/utils/net/hdlc/>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called c101.
+
+ If unsure, say N.
+
+config FARSYNC
+ tristate "FarSync T-Series support"
+ depends on HDLC && PCI
+ ---help---
+ Support for the FarSync T-Series X.21 (and V.35/V.24) cards by
+ FarSite Communications Ltd.
+
+ Synchronous communication is supported on all ports at speeds up to
+ 8Mb/s (128K on V.24) using synchronous PPP, Cisco HDLC, raw HDLC,
+ Frame Relay or X.25/LAPB.
+
+ If you want the module to be automatically loaded when the interface
+ is referenced then you should add "alias hdlcX farsync" to
+ /etc/modprobe.conf for each interface, where X is 0, 1, 2, ..., or
+ simply use "alias hdlc* farsync" to indicate all of them.
+
+ To compile this driver as a module, choose M here: the
+ module will be called farsync.
+
+config DLCI
+ tristate "Frame Relay DLCI support"
+ depends on WAN
+ ---help---
+ Support for the Frame Relay protocol.
+
+ Frame Relay is a fast low-cost way to connect to a remote Internet
+ access provider or to form a private wide area network. The one
+ physical line from your box to the local "switch" (i.e. the entry
+ point to the Frame Relay network, usually at the phone company) can
+ carry several logical point-to-point connections to other computers
+ connected to the Frame Relay network. For a general explanation of
+ the protocol, check out <http://www.mplsforum.org/>.
+
+ To use frame relay, you need supporting hardware (called FRAD) and
+ certain programs from the net-tools package as explained in
+ <file:Documentation/networking/framerelay.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dlci.
+
+config DLCI_COUNT
+ int "Max open DLCI"
+ depends on DLCI
+ default "24"
+ help
+ Maximal number of logical point-to-point frame relay connections
+ (the identifiers of which are called DCLIs) that the driver can
+ handle.
+
+ The default is probably fine.
+
+config DLCI_MAX
+ int "Max DLCI per device"
+ depends on DLCI
+ default "8"
+ help
+ How many logical point-to-point frame relay connections (the
+ identifiers of which are called DCLIs) should be handled by each
+ of your hardware frame relay access devices.
+
+ Go with the default.
+
+config SDLA
+ tristate "SDLA (Sangoma S502/S508) support"
+ depends on DLCI && ISA
+ help
+ Driver for the Sangoma S502A, S502E, and S508 Frame Relay Access
+ Devices.
+
+ These are multi-protocol cards, but only Frame Relay is supported
+ by the driver at this time. Please read
+ <file:Documentation/networking/framerelay.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sdla.
+
+# Wan router core.
+config WAN_ROUTER_DRIVERS
+ bool "WAN router drivers"
+ depends on WAN && WAN_ROUTER
+ ---help---
+ Connect LAN to WAN via Linux box.
+
+ Select driver your card and remember to say Y to "Wan Router."
+ You will need the wan-tools package which is available from
+ <ftp://ftp.sangoma.com/>. For more information read:
+ <file:Documentation/networking/wan-router.txt>.
+
+ Note that the answer to this question won't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about WAN router drivers.
+
+ If unsure, say N.
+
+config VENDOR_SANGOMA
+ tristate "Sangoma WANPIPE(tm) multiprotocol cards"
+ depends on WAN_ROUTER_DRIVERS && WAN_ROUTER && (PCI || ISA) && BROKEN
+ ---help---
+ Driver for S514-PCI/ISA Synchronous Data Link Adapters (SDLA).
+
+ WANPIPE from Sangoma Technologies Inc. <http://www.sangoma.com/>
+ is a family of intelligent multiprotocol WAN adapters with data
+ transfer rates up to 4Mbps. Cards support:
+
+ - X.25, Frame Relay, PPP, Cisco HDLC protocols.
+
+ - API for protocols like HDLC (LAPB), HDLC Streaming, X.25,
+ Frame Relay and BiSync.
+
+ - Ethernet Bridging over Frame Relay protocol.
+
+ - MULTILINK PPP
+
+ - Async PPP (Modem Dialup)
+
+ The next questions will ask you about the protocols you want
+ the driver to support.
+
+ If you have one or more of these cards, say M to this option;
+ and read <file:Documentation/networking/wanpipe.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wanpipe.
+
+config WANPIPE_CHDLC
+ bool "WANPIPE Cisco HDLC support"
+ depends on VENDOR_SANGOMA
+ ---help---
+ Connect a WANPIPE card to a leased line using the Cisco HDLC.
+
+ - Supports Dual Port Cisco HDLC on the S514-PCI/S508-ISA cards
+ which allows user to build applications using the HDLC streaming API.
+
+ - CHDLC Streaming MULTILINK PPP that can bind multiple WANPIPE T1
+ cards into a single logical channel.
+
+ Say Y and the Cisco HDLC support, HDLC streaming API and
+ MULTILINK PPP will be included in the driver.
+
+config WANPIPE_FR
+ bool "WANPIPE Frame Relay support"
+ depends on VENDOR_SANGOMA
+ help
+ Connect a WANPIPE card to a Frame Relay network, or use Frame Felay
+ API to develop custom applications.
+
+ Contains the Ethernet Bridging over Frame Relay feature, where
+ a WANPIPE frame relay link can be directly connected to the Linux
+ kernel bridge. The Frame Relay option is supported on S514-PCI
+ and S508-ISA cards.
+
+ Say Y and the Frame Relay support will be included in the driver.
+
+config WANPIPE_X25
+ bool "WANPIPE X.25 support"
+ depends on VENDOR_SANGOMA
+ help
+ Connect a WANPIPE card to an X.25 network.
+
+ Includes the X.25 API support for custom applications over the
+ X.25 protocol. The X.25 option is supported on S514-PCI and
+ S508-ISA cards.
+
+ Say Y and the X.25 support will be included in the driver.
+
+config WANPIPE_PPP
+ bool "WANPIPE PPP support"
+ depends on VENDOR_SANGOMA
+ help
+ Connect a WANPIPE card to a leased line using Point-to-Point
+ Protocol (PPP).
+
+ The PPP option is supported on S514-PCI/S508-ISA cards.
+
+ Say Y and the PPP support will be included in the driver.
+
+config WANPIPE_MULTPPP
+ bool "WANPIPE Multi-Port PPP support"
+ depends on VENDOR_SANGOMA
+ help
+ Connect a WANPIPE card to a leased line using Point-to-Point
+ Protocol (PPP).
+
+ Uses in-kernel SyncPPP protocol over the Sangoma HDLC Streaming
+ adapter. In this case each Sangoma adapter port can support an
+ independent PPP connection. For example, a single Quad-Port PCI
+ adapter can support up to four independent PPP links. The PPP
+ option is supported on S514-PCI/S508-ISA cards.
+
+ Say Y and the Multi-Port PPP support will be included in the driver.
+
+config CYCLADES_SYNC
+ tristate "Cyclom 2X(tm) cards (EXPERIMENTAL)"
+ depends on WAN_ROUTER_DRIVERS && (PCI || ISA)
+ ---help---
+ Cyclom 2X from Cyclades Corporation <http://www.cyclades.com/> is an
+ intelligent multiprotocol WAN adapter with data transfer rates up to
+ 512 Kbps. These cards support the X.25 and SNA related protocols.
+
+ While no documentation is available at this time please grab the
+ wanconfig tarball in
+ <http://www.conectiva.com.br/~acme/cycsyn-devel/> (with minor changes
+ to make it compile with the current wanrouter include files; efforts
+ are being made to use the original package available at
+ <ftp://ftp.sangoma.com/>).
+
+ Feel free to contact me or the cycsyn-devel mailing list at
+ <acme@conectiva.com.br> and <cycsyn-devel@bazar.conectiva.com.br> for
+ additional details, I hope to have documentation available as soon as
+ possible. (Cyclades Brazil is writing the Documentation).
+
+ The next questions will ask you about the protocols you want the
+ driver to support (for now only X.25 is supported).
+
+ If you have one or more of these cards, say Y to this option.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyclomx.
+
+config CYCLOMX_X25
+ bool "Cyclom 2X X.25 support (EXPERIMENTAL)"
+ depends on CYCLADES_SYNC
+ help
+ Connect a Cyclom 2X card to an X.25 network.
+
+ Enabling X.25 support will enlarge your kernel by about 11 kB.
+
+# X.25 network drivers
+config LAPBETHER
+ tristate "LAPB over Ethernet driver (EXPERIMENTAL)"
+ depends on WAN && LAPB && X25
+ ---help---
+ Driver for a pseudo device (typically called /dev/lapb0) which allows
+ you to open an LAPB point-to-point connection to some other computer
+ on your Ethernet network.
+
+ In order to do this, you need to say Y or M to the driver for your
+ Ethernet card as well as to "LAPB Data Link Driver".
+
+ To compile this driver as a module, choose M here: the
+ module will be called lapbether.
+
+ If unsure, say N.
+
+config X25_ASY
+ tristate "X.25 async driver (EXPERIMENTAL)"
+ depends on WAN && LAPB && X25
+ ---help---
+ Send and receive X.25 frames over regular asynchronous serial
+ lines such as telephone lines equipped with ordinary modems.
+
+ Experts should note that this driver doesn't currently comply with
+ the asynchronous HDLS framing protocols in CCITT recommendation X.25.
+
+ To compile this driver as a module, choose M here: the
+ module will be called x25_asy.
+
+ If unsure, say N.
+
+config SBNI
+ tristate "Granch SBNI12 Leased Line adapter support"
+ depends on WAN && X86
+ ---help---
+ Driver for ISA SBNI12-xx cards which are low cost alternatives to
+ leased line modems.
+
+ You can find more information and last versions of drivers and
+ utilities at <http://www.granch.ru/>. If you have any question you
+ can send email to <sbni@granch.ru>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sbni.
+
+ If unsure, say N.
+
+config SBNI_MULTILINE
+ bool "Multiple line feature support"
+ depends on SBNI
+ help
+ Schedule traffic for some parallel lines, via SBNI12 adapters.
+
+ If you have two computers connected with two parallel lines it's
+ possible to increase transfer rate nearly twice. You should have
+ a program named 'sbniconfig' to configure adapters.
+
+ If unsure, say N.
+
+endmenu
+
diff --git a/drivers/net/wan/Makefile b/drivers/net/wan/Makefile
new file mode 100644
index 00000000000..ce6c56b903e
--- /dev/null
+++ b/drivers/net/wan/Makefile
@@ -0,0 +1,86 @@
+#
+# Makefile for the Linux network (wan) device drivers.
+#
+# 3 Aug 2000, Christoph Hellwig <hch@infradead.org>
+# Rewritten to use lists instead of if-statements.
+#
+
+wanpipe-y := sdlamain.o sdla_ft1.o
+wanpipe-$(CONFIG_WANPIPE_X25) += sdla_x25.o
+wanpipe-$(CONFIG_WANPIPE_FR) += sdla_fr.o
+wanpipe-$(CONFIG_WANPIPE_CHDLC) += sdla_chdlc.o
+wanpipe-$(CONFIG_WANPIPE_PPP) += sdla_ppp.o
+wanpipe-$(CONFIG_WANPIPE_MULTPPP) += wanpipe_multppp.o
+wanpipe-objs := $(wanpipe-y)
+
+cyclomx-y := cycx_main.o
+cyclomx-$(CONFIG_CYCLOMX_X25) += cycx_x25.o
+cyclomx-objs := $(cyclomx-y)
+
+hdlc-y := hdlc_generic.o
+hdlc-$(CONFIG_HDLC_RAW) += hdlc_raw.o
+hdlc-$(CONFIG_HDLC_RAW_ETH) += hdlc_raw_eth.o
+hdlc-$(CONFIG_HDLC_CISCO) += hdlc_cisco.o
+hdlc-$(CONFIG_HDLC_FR) += hdlc_fr.o
+hdlc-$(CONFIG_HDLC_PPP) += hdlc_ppp.o
+hdlc-$(CONFIG_HDLC_X25) += hdlc_x25.o
+hdlc-objs := $(hdlc-y)
+
+pc300-y := pc300_drv.o
+pc300-$(CONFIG_PC300_MLPPP) += pc300_tty.o
+pc300-objs := $(pc300-y)
+
+obj-$(CONFIG_HOSTESS_SV11) += z85230.o syncppp.o hostess_sv11.o
+obj-$(CONFIG_SEALEVEL_4021) += z85230.o syncppp.o sealevel.o
+obj-$(CONFIG_COSA) += syncppp.o cosa.o
+obj-$(CONFIG_FARSYNC) += syncppp.o farsync.o
+obj-$(CONFIG_DSCC4) += dscc4.o
+obj-$(CONFIG_LANMEDIA) += syncppp.o
+obj-$(CONFIG_SYNCLINK_SYNCPPP) += syncppp.o
+obj-$(CONFIG_X25_ASY) += x25_asy.o
+
+obj-$(CONFIG_LANMEDIA) += lmc/
+
+obj-$(CONFIG_DLCI) += dlci.o
+obj-$(CONFIG_SDLA) += sdla.o
+ifeq ($(CONFIG_WANPIPE_MULTPPP),y)
+ obj-$(CONFIG_VENDOR_SANGOMA) += sdladrv.o wanpipe.o syncppp.o
+else
+ obj-$(CONFIG_VENDOR_SANGOMA) += sdladrv.o wanpipe.o
+endif
+obj-$(CONFIG_CYCLADES_SYNC) += cycx_drv.o cyclomx.o
+obj-$(CONFIG_LAPBETHER) += lapbether.o
+obj-$(CONFIG_SBNI) += sbni.o
+obj-$(CONFIG_PC300) += pc300.o
+obj-$(CONFIG_HDLC) += hdlc.o
+ifeq ($(CONFIG_HDLC_PPP),y)
+ obj-$(CONFIG_HDLC) += syncppp.o
+endif
+obj-$(CONFIG_N2) += n2.o
+obj-$(CONFIG_C101) += c101.o
+obj-$(CONFIG_WANXL) += wanxl.o
+obj-$(CONFIG_PCI200SYN) += pci200syn.o
+
+clean-files := wanxlfw.inc
+$(obj)/wanxl.o: $(obj)/wanxlfw.inc
+
+ifeq ($(CONFIG_WANXL_BUILD_FIRMWARE),y)
+ifeq ($(ARCH),m68k)
+ AS68K = $(AS)
+ LD68K = $(LD)
+else
+ AS68K = as68k
+ LD68K = ld68k
+endif
+
+quiet_cmd_build_wanxlfw = BLD FW $@
+ cmd_build_wanxlfw = \
+ $(CPP) -Wp,-MD,$(depfile) -I$(srctree)/include $< | $(AS68K) -m68360 -o $(obj)/wanxlfw.o; \
+ $(LD68K) --oformat binary -Ttext 0x1000 $(obj)/wanxlfw.o -o $(obj)/wanxlfw.bin; \
+ hexdump -ve '"\n" 16/1 "0x%02X,"' $(obj)/wanxlfw.bin | sed 's/0x ,//g;1s/^/static u8 firmware[]={/;$$s/,$$/\n};\n/' >$(obj)/wanxlfw.inc; \
+ rm -f $(obj)/wanxlfw.bin $(obj)/wanxlfw.o
+
+$(obj)/wanxlfw.inc: $(src)/wanxlfw.S
+ $(call if_changed_dep,build_wanxlfw)
+targets += wanxlfw.inc
+endif
diff --git a/drivers/net/wan/c101.c b/drivers/net/wan/c101.c
new file mode 100644
index 00000000000..43d854ace23
--- /dev/null
+++ b/drivers/net/wan/c101.c
@@ -0,0 +1,446 @@
+/*
+ * Moxa C101 synchronous serial card driver for Linux
+ *
+ * Copyright (C) 2000-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * For information see http://hq.pm.waw.pl/hdlc/
+ *
+ * Sources of information:
+ * Hitachi HD64570 SCA User's Manual
+ * Moxa C101 User's Manual
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+
+#include "hd64570.h"
+
+
+static const char* version = "Moxa C101 driver version: 1.15";
+static const char* devname = "C101";
+
+#undef DEBUG_PKT
+#define DEBUG_RINGS
+
+#define C101_PAGE 0x1D00
+#define C101_DTR 0x1E00
+#define C101_SCA 0x1F00
+#define C101_WINDOW_SIZE 0x2000
+#define C101_MAPPED_RAM_SIZE 0x4000
+
+#define RAM_SIZE (256 * 1024)
+#define TX_RING_BUFFERS 10
+#define RX_RING_BUFFERS ((RAM_SIZE - C101_WINDOW_SIZE) / \
+ (sizeof(pkt_desc) + HDLC_MAX_MRU) - TX_RING_BUFFERS)
+
+#define CLOCK_BASE 9830400 /* 9.8304 MHz */
+#define PAGE0_ALWAYS_MAPPED
+
+static char *hw; /* pointer to hw=xxx command line string */
+
+
+typedef struct card_s {
+ struct net_device *dev;
+ spinlock_t lock; /* TX lock */
+ u8 __iomem *win0base; /* ISA window base address */
+ u32 phy_winbase; /* ISA physical base address */
+ sync_serial_settings settings;
+ int rxpart; /* partial frame received, next frame invalid*/
+ unsigned short encoding;
+ unsigned short parity;
+ u16 rx_ring_buffers; /* number of buffers in a ring */
+ u16 tx_ring_buffers;
+ u16 buff_offset; /* offset of first buffer of first channel */
+ u16 rxin; /* rx ring buffer 'in' pointer */
+ u16 txin; /* tx ring buffer 'in' and 'last' pointers */
+ u16 txlast;
+ u8 rxs, txs, tmc; /* SCA registers */
+ u8 irq; /* IRQ (3-15) */
+ u8 page;
+
+ struct card_s *next_card;
+}card_t;
+
+typedef card_t port_t;
+
+static card_t *first_card;
+static card_t **new_card = &first_card;
+
+
+#define sca_in(reg, card) readb((card)->win0base + C101_SCA + (reg))
+#define sca_out(value, reg, card) writeb(value, (card)->win0base + C101_SCA + (reg))
+#define sca_inw(reg, card) readw((card)->win0base + C101_SCA + (reg))
+
+/* EDA address register must be set in EDAL, EDAH order - 8 bit ISA bus */
+#define sca_outw(value, reg, card) do { \
+ writeb(value & 0xFF, (card)->win0base + C101_SCA + (reg)); \
+ writeb((value >> 8 ) & 0xFF, (card)->win0base + C101_SCA + (reg+1));\
+} while(0)
+
+#define port_to_card(port) (port)
+#define log_node(port) (0)
+#define phy_node(port) (0)
+#define winsize(card) (C101_WINDOW_SIZE)
+#define win0base(card) ((card)->win0base)
+#define winbase(card) ((card)->win0base + 0x2000)
+#define get_port(card, port) (card)
+static void sca_msci_intr(port_t *port);
+
+
+static inline u8 sca_get_page(card_t *card)
+{
+ return card->page;
+}
+
+static inline void openwin(card_t *card, u8 page)
+{
+ card->page = page;
+ writeb(page, card->win0base + C101_PAGE);
+}
+
+
+#include "hd6457x.c"
+
+
+static void sca_msci_intr(port_t *port)
+{
+ struct net_device *dev = port_to_dev(port);
+ card_t* card = port_to_card(port);
+ u8 stat = sca_in(MSCI1_OFFSET + ST1, card); /* read MSCI ST1 status */
+
+ /* Reset MSCI TX underrun status bit */
+ sca_out(stat & ST1_UDRN, MSCI0_OFFSET + ST1, card);
+
+ if (stat & ST1_UDRN) {
+ struct net_device_stats *stats = hdlc_stats(dev);
+ stats->tx_errors++; /* TX Underrun error detected */
+ stats->tx_fifo_errors++;
+ }
+
+ /* Reset MSCI CDCD status bit - uses ch#2 DCD input */
+ sca_out(stat & ST1_CDCD, MSCI1_OFFSET + ST1, card);
+
+ if (stat & ST1_CDCD)
+ hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, card) & ST3_DCD),
+ dev);
+}
+
+
+static void c101_set_iface(port_t *port)
+{
+ u8 rxs = port->rxs & CLK_BRG_MASK;
+ u8 txs = port->txs & CLK_BRG_MASK;
+
+ switch(port->settings.clock_type) {
+ case CLOCK_INT:
+ rxs |= CLK_BRG_RX; /* TX clock */
+ txs |= CLK_RXCLK_TX; /* BRG output */
+ break;
+
+ case CLOCK_TXINT:
+ rxs |= CLK_LINE_RX; /* RXC input */
+ txs |= CLK_BRG_TX; /* BRG output */
+ break;
+
+ case CLOCK_TXFROMRX:
+ rxs |= CLK_LINE_RX; /* RXC input */
+ txs |= CLK_RXCLK_TX; /* RX clock */
+ break;
+
+ default: /* EXTernal clock */
+ rxs |= CLK_LINE_RX; /* RXC input */
+ txs |= CLK_LINE_TX; /* TXC input */
+ }
+
+ port->rxs = rxs;
+ port->txs = txs;
+ sca_out(rxs, MSCI1_OFFSET + RXS, port);
+ sca_out(txs, MSCI1_OFFSET + TXS, port);
+ sca_set_port(port);
+}
+
+
+static int c101_open(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ int result;
+
+ result = hdlc_open(dev);
+ if (result)
+ return result;
+
+ writeb(1, port->win0base + C101_DTR);
+ sca_out(0, MSCI1_OFFSET + CTL, port); /* RTS uses ch#2 output */
+ sca_open(dev);
+ /* DCD is connected to port 2 !@#$%^& - disable MSCI0 CDCD interrupt */
+ sca_out(IE1_UDRN, MSCI0_OFFSET + IE1, port);
+ sca_out(IE0_TXINT, MSCI0_OFFSET + IE0, port);
+
+ hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, port) & ST3_DCD), dev);
+ printk(KERN_DEBUG "0x%X\n", sca_in(MSCI1_OFFSET + ST3, port));
+
+ /* enable MSCI1 CDCD interrupt */
+ sca_out(IE1_CDCD, MSCI1_OFFSET + IE1, port);
+ sca_out(IE0_RXINTA, MSCI1_OFFSET + IE0, port);
+ sca_out(0x48, IER0, port); /* TXINT #0 and RXINT #1 */
+ c101_set_iface(port);
+ return 0;
+}
+
+
+static int c101_close(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+
+ sca_close(dev);
+ writeb(0, port->win0base + C101_DTR);
+ sca_out(CTL_NORTS, MSCI1_OFFSET + CTL, port);
+ hdlc_close(dev);
+ return 0;
+}
+
+
+static int c101_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings new_line;
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ port_t *port = dev_to_port(dev);
+
+#ifdef DEBUG_RINGS
+ if (cmd == SIOCDEVPRIVATE) {
+ sca_dump_rings(dev);
+ printk(KERN_DEBUG "MSCI1: ST: %02x %02x %02x %02x\n",
+ sca_in(MSCI1_OFFSET + ST0, port),
+ sca_in(MSCI1_OFFSET + ST1, port),
+ sca_in(MSCI1_OFFSET + ST2, port),
+ sca_in(MSCI1_OFFSET + ST3, port));
+ return 0;
+ }
+#endif
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(line, &port->settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_SYNC_SERIAL:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&new_line, line, size))
+ return -EFAULT;
+
+ if (new_line.clock_type != CLOCK_EXT &&
+ new_line.clock_type != CLOCK_TXFROMRX &&
+ new_line.clock_type != CLOCK_INT &&
+ new_line.clock_type != CLOCK_TXINT)
+ return -EINVAL; /* No such clock setting */
+
+ if (new_line.loopback != 0 && new_line.loopback != 1)
+ return -EINVAL;
+
+ memcpy(&port->settings, &new_line, size); /* Update settings */
+ c101_set_iface(port);
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+
+
+static void c101_destroy_card(card_t *card)
+{
+ readb(card->win0base + C101_PAGE); /* Resets SCA? */
+
+ if (card->irq)
+ free_irq(card->irq, card);
+
+ if (card->win0base) {
+ iounmap(card->win0base);
+ release_mem_region(card->phy_winbase, C101_MAPPED_RAM_SIZE);
+ }
+
+ free_netdev(card->dev);
+
+ kfree(card);
+}
+
+
+
+static int __init c101_run(unsigned long irq, unsigned long winbase)
+{
+ struct net_device *dev;
+ hdlc_device *hdlc;
+ card_t *card;
+ int result;
+
+ if (irq<3 || irq>15 || irq == 6) /* FIXME */ {
+ printk(KERN_ERR "c101: invalid IRQ value\n");
+ return -ENODEV;
+ }
+
+ if (winbase < 0xC0000 || winbase > 0xDFFFF || (winbase & 0x3FFF) !=0) {
+ printk(KERN_ERR "c101: invalid RAM value\n");
+ return -ENODEV;
+ }
+
+ card = kmalloc(sizeof(card_t), GFP_KERNEL);
+ if (card == NULL) {
+ printk(KERN_ERR "c101: unable to allocate memory\n");
+ return -ENOBUFS;
+ }
+ memset(card, 0, sizeof(card_t));
+
+ card->dev = alloc_hdlcdev(card);
+ if (!card->dev) {
+ printk(KERN_ERR "c101: unable to allocate memory\n");
+ kfree(card);
+ return -ENOBUFS;
+ }
+
+ if (request_irq(irq, sca_intr, 0, devname, card)) {
+ printk(KERN_ERR "c101: could not allocate IRQ\n");
+ c101_destroy_card(card);
+ return(-EBUSY);
+ }
+ card->irq = irq;
+
+ if (!request_mem_region(winbase, C101_MAPPED_RAM_SIZE, devname)) {
+ printk(KERN_ERR "c101: could not request RAM window\n");
+ c101_destroy_card(card);
+ return(-EBUSY);
+ }
+ card->phy_winbase = winbase;
+ card->win0base = ioremap(winbase, C101_MAPPED_RAM_SIZE);
+ if (!card->win0base) {
+ printk(KERN_ERR "c101: could not map I/O address\n");
+ c101_destroy_card(card);
+ return -EBUSY;
+ }
+
+ card->tx_ring_buffers = TX_RING_BUFFERS;
+ card->rx_ring_buffers = RX_RING_BUFFERS;
+ card->buff_offset = C101_WINDOW_SIZE; /* Bytes 1D00-1FFF reserved */
+
+ readb(card->win0base + C101_PAGE); /* Resets SCA? */
+ udelay(100);
+ writeb(0, card->win0base + C101_PAGE);
+ writeb(0, card->win0base + C101_DTR); /* Power-up for RAM? */
+
+ sca_init(card, 0);
+
+ dev = port_to_dev(card);
+ hdlc = dev_to_hdlc(dev);
+
+ spin_lock_init(&card->lock);
+ SET_MODULE_OWNER(dev);
+ dev->irq = irq;
+ dev->mem_start = winbase;
+ dev->mem_end = winbase + C101_MAPPED_RAM_SIZE - 1;
+ dev->tx_queue_len = 50;
+ dev->do_ioctl = c101_ioctl;
+ dev->open = c101_open;
+ dev->stop = c101_close;
+ hdlc->attach = sca_attach;
+ hdlc->xmit = sca_xmit;
+ card->settings.clock_type = CLOCK_EXT;
+
+ result = register_hdlc_device(dev);
+ if (result) {
+ printk(KERN_WARNING "c101: unable to register hdlc device\n");
+ c101_destroy_card(card);
+ return result;
+ }
+
+ sca_init_sync_port(card); /* Set up C101 memory */
+ hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, card) & ST3_DCD), dev);
+
+ printk(KERN_INFO "%s: Moxa C101 on IRQ%u,"
+ " using %u TX + %u RX packets rings\n",
+ dev->name, card->irq,
+ card->tx_ring_buffers, card->rx_ring_buffers);
+
+ *new_card = card;
+ new_card = &card->next_card;
+ return 0;
+}
+
+
+
+static int __init c101_init(void)
+{
+ if (hw == NULL) {
+#ifdef MODULE
+ printk(KERN_INFO "c101: no card initialized\n");
+#endif
+ return -ENOSYS; /* no parameters specified, abort */
+ }
+
+ printk(KERN_INFO "%s\n", version);
+
+ do {
+ unsigned long irq, ram;
+
+ irq = simple_strtoul(hw, &hw, 0);
+
+ if (*hw++ != ',')
+ break;
+ ram = simple_strtoul(hw, &hw, 0);
+
+ if (*hw == ':' || *hw == '\x0')
+ c101_run(irq, ram);
+
+ if (*hw == '\x0')
+ return first_card ? 0 : -ENOSYS;
+ }while(*hw++ == ':');
+
+ printk(KERN_ERR "c101: invalid hardware parameters\n");
+ return first_card ? 0 : -ENOSYS;
+}
+
+
+static void __exit c101_cleanup(void)
+{
+ card_t *card = first_card;
+
+ while (card) {
+ card_t *ptr = card;
+ card = card->next_card;
+ unregister_hdlc_device(port_to_dev(ptr));
+ c101_destroy_card(ptr);
+ }
+}
+
+
+module_init(c101_init);
+module_exit(c101_cleanup);
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("Moxa C101 serial port driver");
+MODULE_LICENSE("GPL v2");
+module_param(hw, charp, 0444); /* hw=irq,ram:irq,... */
diff --git a/drivers/net/wan/cosa.c b/drivers/net/wan/cosa.c
new file mode 100644
index 00000000000..921a573372e
--- /dev/null
+++ b/drivers/net/wan/cosa.c
@@ -0,0 +1,2100 @@
+/* $Id: cosa.c,v 1.31 2000/03/08 17:47:16 kas Exp $ */
+
+/*
+ * Copyright (C) 1995-1997 Jan "Yenya" Kasprzak <kas@fi.muni.cz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * The driver for the SRP and COSA synchronous serial cards.
+ *
+ * HARDWARE INFO
+ *
+ * Both cards are developed at the Institute of Computer Science,
+ * Masaryk University (http://www.ics.muni.cz/). The hardware is
+ * developed by Jiri Novotny <novotny@ics.muni.cz>. More information
+ * and the photo of both cards is available at
+ * http://www.pavoucek.cz/cosa.html. The card documentation, firmwares
+ * and other goods can be downloaded from ftp://ftp.ics.muni.cz/pub/cosa/.
+ * For Linux-specific utilities, see below in the "Software info" section.
+ * If you want to order the card, contact Jiri Novotny.
+ *
+ * The SRP (serial port?, the Czech word "srp" means "sickle") card
+ * is a 2-port intelligent (with its own 8-bit CPU) synchronous serial card
+ * with V.24 interfaces up to 80kb/s each.
+ *
+ * The COSA (communication serial adapter?, the Czech word "kosa" means
+ * "scythe") is a next-generation sync/async board with two interfaces
+ * - currently any of V.24, X.21, V.35 and V.36 can be selected.
+ * It has a 16-bit SAB80166 CPU and can do up to 10 Mb/s per channel.
+ * The 8-channels version is in development.
+ *
+ * Both types have downloadable firmware and communicate via ISA DMA.
+ * COSA can be also a bus-mastering device.
+ *
+ * SOFTWARE INFO
+ *
+ * The homepage of the Linux driver is at http://www.fi.muni.cz/~kas/cosa/.
+ * The CVS tree of Linux driver can be viewed there, as well as the
+ * firmware binaries and user-space utilities for downloading the firmware
+ * into the card and setting up the card.
+ *
+ * The Linux driver (unlike the present *BSD drivers :-) can work even
+ * for the COSA and SRP in one computer and allows each channel to work
+ * in one of the three modes (character device, Cisco HDLC, Sync PPP).
+ *
+ * AUTHOR
+ *
+ * The Linux driver was written by Jan "Yenya" Kasprzak <kas@fi.muni.cz>.
+ *
+ * You can mail me bugfixes and even success reports. I am especially
+ * interested in the SMP and/or muliti-channel success/failure reports
+ * (I wonder if I did the locking properly :-).
+ *
+ * THE AUTHOR USED THE FOLLOWING SOURCES WHEN PROGRAMMING THE DRIVER
+ *
+ * The COSA/SRP NetBSD driver by Zdenek Salvet and Ivos Cernohlavek
+ * The skeleton.c by Donald Becker
+ * The SDL Riscom/N2 driver by Mike Natale
+ * The Comtrol Hostess SV11 driver by Alan Cox
+ * The Sync PPP/Cisco HDLC layer (syncppp.c) ported to Linux by Alan Cox
+ */
+/*
+ * 5/25/1999 : Marcelo Tosatti <marcelo@conectiva.com.br>
+ * fixed a deadlock in cosa_sppp_open
+ */
+
+/* ---------- Headers, macros, data structures ---------- */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+
+#undef COSA_SLOW_IO /* for testing purposes only */
+#undef REALLY_SLOW_IO
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+
+#include <net/syncppp.h>
+#include "cosa.h"
+
+/* Maximum length of the identification string. */
+#define COSA_MAX_ID_STRING 128
+
+/* Maximum length of the channel name */
+#define COSA_MAX_NAME (sizeof("cosaXXXcXXX")+1)
+
+/* Per-channel data structure */
+
+struct channel_data {
+ void *if_ptr; /* General purpose pointer (used by SPPP) */
+ int usage; /* Usage count; >0 for chrdev, -1 for netdev */
+ int num; /* Number of the channel */
+ struct cosa_data *cosa; /* Pointer to the per-card structure */
+ int txsize; /* Size of transmitted data */
+ char *txbuf; /* Transmit buffer */
+ char name[COSA_MAX_NAME]; /* channel name */
+
+ /* The HW layer interface */
+ /* routine called from the RX interrupt */
+ char *(*setup_rx)(struct channel_data *channel, int size);
+ /* routine called when the RX is done (from the EOT interrupt) */
+ int (*rx_done)(struct channel_data *channel);
+ /* routine called when the TX is done (from the EOT interrupt) */
+ int (*tx_done)(struct channel_data *channel, int size);
+
+ /* Character device parts */
+ struct semaphore rsem, wsem;
+ char *rxdata;
+ int rxsize;
+ wait_queue_head_t txwaitq, rxwaitq;
+ int tx_status, rx_status;
+
+ /* SPPP/HDLC device parts */
+ struct ppp_device pppdev;
+ struct sk_buff *rx_skb, *tx_skb;
+ struct net_device_stats stats;
+};
+
+/* cosa->firmware_status bits */
+#define COSA_FW_RESET (1<<0) /* Is the ROM monitor active? */
+#define COSA_FW_DOWNLOAD (1<<1) /* Is the microcode downloaded? */
+#define COSA_FW_START (1<<2) /* Is the microcode running? */
+
+struct cosa_data {
+ int num; /* Card number */
+ char name[COSA_MAX_NAME]; /* Card name - e.g "cosa0" */
+ unsigned int datareg, statusreg; /* I/O ports */
+ unsigned short irq, dma; /* IRQ and DMA number */
+ unsigned short startaddr; /* Firmware start address */
+ unsigned short busmaster; /* Use busmastering? */
+ int nchannels; /* # of channels on this card */
+ int driver_status; /* For communicating with firmware */
+ int firmware_status; /* Downloaded, reseted, etc. */
+ long int rxbitmap, txbitmap; /* Bitmap of channels who are willing to send/receive data */
+ long int rxtx; /* RX or TX in progress? */
+ int enabled;
+ int usage; /* usage count */
+ int txchan, txsize, rxsize;
+ struct channel_data *rxchan;
+ char *bouncebuf;
+ char *txbuf, *rxbuf;
+ struct channel_data *chan;
+ spinlock_t lock; /* For exclusive operations on this structure */
+ char id_string[COSA_MAX_ID_STRING]; /* ROM monitor ID string */
+ char *type; /* card type */
+};
+
+/*
+ * Define this if you want all the possible ports to be autoprobed.
+ * It is here but it probably is not a good idea to use this.
+ */
+/* #define COSA_ISA_AUTOPROBE 1 */
+
+/*
+ * Character device major number. 117 was allocated for us.
+ * The value of 0 means to allocate a first free one.
+ */
+static int cosa_major = 117;
+
+/*
+ * Encoding of the minor numbers:
+ * The lowest CARD_MINOR_BITS bits means the channel on the single card,
+ * the highest bits means the card number.
+ */
+#define CARD_MINOR_BITS 4 /* How many bits in minor number are reserved
+ * for the single card */
+/*
+ * The following depends on CARD_MINOR_BITS. Unfortunately, the "MODULE_STRING"
+ * macro doesn't like anything other than the raw number as an argument :-(
+ */
+#define MAX_CARDS 16
+/* #define MAX_CARDS (1 << (8-CARD_MINOR_BITS)) */
+
+#define DRIVER_RX_READY 0x0001
+#define DRIVER_TX_READY 0x0002
+#define DRIVER_TXMAP_SHIFT 2
+#define DRIVER_TXMAP_MASK 0x0c /* FIXME: 0xfc for 8-channel version */
+
+/*
+ * for cosa->rxtx - indicates whether either transmit or receive is
+ * in progress. These values are mean number of the bit.
+ */
+#define TXBIT 0
+#define RXBIT 1
+#define IRQBIT 2
+
+#define COSA_MTU 2000 /* FIXME: I don't know this exactly */
+
+#undef DEBUG_DATA //1 /* Dump the data read or written to the channel */
+#undef DEBUG_IRQS //1 /* Print the message when the IRQ is received */
+#undef DEBUG_IO //1 /* Dump the I/O traffic */
+
+#define TX_TIMEOUT (5*HZ)
+
+/* Maybe the following should be allocated dynamically */
+static struct cosa_data cosa_cards[MAX_CARDS];
+static int nr_cards;
+
+#ifdef COSA_ISA_AUTOPROBE
+static int io[MAX_CARDS+1] = { 0x220, 0x228, 0x210, 0x218, 0, };
+/* NOTE: DMA is not autoprobed!!! */
+static int dma[MAX_CARDS+1] = { 1, 7, 1, 7, 1, 7, 1, 7, 0, };
+#else
+static int io[MAX_CARDS+1];
+static int dma[MAX_CARDS+1];
+#endif
+/* IRQ can be safely autoprobed */
+static int irq[MAX_CARDS+1] = { -1, -1, -1, -1, -1, -1, 0, };
+
+/* for class stuff*/
+static struct class_simple *cosa_class;
+
+#ifdef MODULE
+module_param_array(io, int, NULL, 0);
+MODULE_PARM_DESC(io, "The I/O bases of the COSA or SRP cards");
+module_param_array(irq, int, NULL, 0);
+MODULE_PARM_DESC(irq, "The IRQ lines of the COSA or SRP cards");
+module_param_array(dma, int, NULL, 0);
+MODULE_PARM_DESC(dma, "The DMA channels of the COSA or SRP cards");
+
+MODULE_AUTHOR("Jan \"Yenya\" Kasprzak, <kas@fi.muni.cz>");
+MODULE_DESCRIPTION("Modular driver for the COSA or SRP synchronous card");
+MODULE_LICENSE("GPL");
+#endif
+
+/* I use this mainly for testing purposes */
+#ifdef COSA_SLOW_IO
+#define cosa_outb outb_p
+#define cosa_outw outw_p
+#define cosa_inb inb_p
+#define cosa_inw inw_p
+#else
+#define cosa_outb outb
+#define cosa_outw outw
+#define cosa_inb inb
+#define cosa_inw inw
+#endif
+
+#define is_8bit(cosa) (!(cosa->datareg & 0x08))
+
+#define cosa_getstatus(cosa) (cosa_inb(cosa->statusreg))
+#define cosa_putstatus(cosa, stat) (cosa_outb(stat, cosa->statusreg))
+#define cosa_getdata16(cosa) (cosa_inw(cosa->datareg))
+#define cosa_getdata8(cosa) (cosa_inb(cosa->datareg))
+#define cosa_putdata16(cosa, dt) (cosa_outw(dt, cosa->datareg))
+#define cosa_putdata8(cosa, dt) (cosa_outb(dt, cosa->datareg))
+
+/* Initialization stuff */
+static int cosa_probe(int ioaddr, int irq, int dma);
+
+/* HW interface */
+static void cosa_enable_rx(struct channel_data *chan);
+static void cosa_disable_rx(struct channel_data *chan);
+static int cosa_start_tx(struct channel_data *channel, char *buf, int size);
+static void cosa_kick(struct cosa_data *cosa);
+static int cosa_dma_able(struct channel_data *chan, char *buf, int data);
+
+/* SPPP/HDLC stuff */
+static void sppp_channel_init(struct channel_data *chan);
+static void sppp_channel_delete(struct channel_data *chan);
+static int cosa_sppp_open(struct net_device *d);
+static int cosa_sppp_close(struct net_device *d);
+static void cosa_sppp_timeout(struct net_device *d);
+static int cosa_sppp_tx(struct sk_buff *skb, struct net_device *d);
+static char *sppp_setup_rx(struct channel_data *channel, int size);
+static int sppp_rx_done(struct channel_data *channel);
+static int sppp_tx_done(struct channel_data *channel, int size);
+static int cosa_sppp_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+static struct net_device_stats *cosa_net_stats(struct net_device *dev);
+
+/* Character device */
+static void chardev_channel_init(struct channel_data *chan);
+static char *chrdev_setup_rx(struct channel_data *channel, int size);
+static int chrdev_rx_done(struct channel_data *channel);
+static int chrdev_tx_done(struct channel_data *channel, int size);
+static ssize_t cosa_read(struct file *file,
+ char __user *buf, size_t count, loff_t *ppos);
+static ssize_t cosa_write(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos);
+static unsigned int cosa_poll(struct file *file, poll_table *poll);
+static int cosa_open(struct inode *inode, struct file *file);
+static int cosa_release(struct inode *inode, struct file *file);
+static int cosa_chardev_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+#ifdef COSA_FASYNC_WORKING
+static int cosa_fasync(struct inode *inode, struct file *file, int on);
+#endif
+
+static struct file_operations cosa_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = cosa_read,
+ .write = cosa_write,
+ .poll = cosa_poll,
+ .ioctl = cosa_chardev_ioctl,
+ .open = cosa_open,
+ .release = cosa_release,
+#ifdef COSA_FASYNC_WORKING
+ .fasync = cosa_fasync,
+#endif
+};
+
+/* Ioctls */
+static int cosa_start(struct cosa_data *cosa, int address);
+static int cosa_reset(struct cosa_data *cosa);
+static int cosa_download(struct cosa_data *cosa, void __user *a);
+static int cosa_readmem(struct cosa_data *cosa, void __user *a);
+
+/* COSA/SRP ROM monitor */
+static int download(struct cosa_data *cosa, const char __user *data, int addr, int len);
+static int startmicrocode(struct cosa_data *cosa, int address);
+static int readmem(struct cosa_data *cosa, char __user *data, int addr, int len);
+static int cosa_reset_and_read_id(struct cosa_data *cosa, char *id);
+
+/* Auxilliary functions */
+static int get_wait_data(struct cosa_data *cosa);
+static int put_wait_data(struct cosa_data *cosa, int data);
+static int puthexnumber(struct cosa_data *cosa, int number);
+static void put_driver_status(struct cosa_data *cosa);
+static void put_driver_status_nolock(struct cosa_data *cosa);
+
+/* Interrupt handling */
+static irqreturn_t cosa_interrupt(int irq, void *cosa, struct pt_regs *regs);
+
+/* I/O ops debugging */
+#ifdef DEBUG_IO
+static void debug_data_in(struct cosa_data *cosa, int data);
+static void debug_data_out(struct cosa_data *cosa, int data);
+static void debug_data_cmd(struct cosa_data *cosa, int data);
+static void debug_status_in(struct cosa_data *cosa, int status);
+static void debug_status_out(struct cosa_data *cosa, int status);
+#endif
+
+
+/* ---------- Initialization stuff ---------- */
+
+static int __init cosa_init(void)
+{
+ int i, err = 0;
+
+ printk(KERN_INFO "cosa v1.08 (c) 1997-2000 Jan Kasprzak <kas@fi.muni.cz>\n");
+#ifdef CONFIG_SMP
+ printk(KERN_INFO "cosa: SMP found. Please mail any success/failure reports to the author.\n");
+#endif
+ if (cosa_major > 0) {
+ if (register_chrdev(cosa_major, "cosa", &cosa_fops)) {
+ printk(KERN_WARNING "cosa: unable to get major %d\n",
+ cosa_major);
+ err = -EIO;
+ goto out;
+ }
+ } else {
+ if (!(cosa_major=register_chrdev(0, "cosa", &cosa_fops))) {
+ printk(KERN_WARNING "cosa: unable to register chardev\n");
+ err = -EIO;
+ goto out;
+ }
+ }
+ for (i=0; i<MAX_CARDS; i++)
+ cosa_cards[i].num = -1;
+ for (i=0; io[i] != 0 && i < MAX_CARDS; i++)
+ cosa_probe(io[i], irq[i], dma[i]);
+ if (!nr_cards) {
+ printk(KERN_WARNING "cosa: no devices found.\n");
+ unregister_chrdev(cosa_major, "cosa");
+ err = -ENODEV;
+ goto out;
+ }
+ devfs_mk_dir("cosa");
+ cosa_class = class_simple_create(THIS_MODULE, "cosa");
+ if (IS_ERR(cosa_class)) {
+ err = PTR_ERR(cosa_class);
+ goto out_chrdev;
+ }
+ for (i=0; i<nr_cards; i++) {
+ class_simple_device_add(cosa_class, MKDEV(cosa_major, i),
+ NULL, "cosa%d", i);
+ err = devfs_mk_cdev(MKDEV(cosa_major, i),
+ S_IFCHR|S_IRUSR|S_IWUSR,
+ "cosa/%d", i);
+ if (err) {
+ class_simple_device_remove(MKDEV(cosa_major, i));
+ goto out_chrdev;
+ }
+ }
+ err = 0;
+ goto out;
+
+out_chrdev:
+ unregister_chrdev(cosa_major, "cosa");
+out:
+ return err;
+}
+module_init(cosa_init);
+
+static void __exit cosa_exit(void)
+{
+ struct cosa_data *cosa;
+ int i;
+ printk(KERN_INFO "Unloading the cosa module\n");
+
+ for (i=0; i<nr_cards; i++) {
+ class_simple_device_remove(MKDEV(cosa_major, i));
+ devfs_remove("cosa/%d", i);
+ }
+ class_simple_destroy(cosa_class);
+ devfs_remove("cosa");
+ for (cosa=cosa_cards; nr_cards--; cosa++) {
+ /* Clean up the per-channel data */
+ for (i=0; i<cosa->nchannels; i++) {
+ /* Chardev driver has no alloc'd per-channel data */
+ sppp_channel_delete(cosa->chan+i);
+ }
+ /* Clean up the per-card data */
+ kfree(cosa->chan);
+ kfree(cosa->bouncebuf);
+ free_irq(cosa->irq, cosa);
+ free_dma(cosa->dma);
+ release_region(cosa->datareg,is_8bit(cosa)?2:4);
+ }
+ unregister_chrdev(cosa_major, "cosa");
+}
+module_exit(cosa_exit);
+
+/*
+ * This function should register all the net devices needed for the
+ * single channel.
+ */
+static __inline__ void channel_init(struct channel_data *chan)
+{
+ sprintf(chan->name, "cosa%dc%d", chan->cosa->num, chan->num);
+
+ /* Initialize the chardev data structures */
+ chardev_channel_init(chan);
+
+ /* Register the sppp interface */
+ sppp_channel_init(chan);
+}
+
+static int cosa_probe(int base, int irq, int dma)
+{
+ struct cosa_data *cosa = cosa_cards+nr_cards;
+ int i, err = 0;
+
+ memset(cosa, 0, sizeof(struct cosa_data));
+
+ /* Checking validity of parameters: */
+ /* IRQ should be 2-7 or 10-15; negative IRQ means autoprobe */
+ if ((irq >= 0 && irq < 2) || irq > 15 || (irq < 10 && irq > 7)) {
+ printk (KERN_INFO "cosa_probe: invalid IRQ %d\n", irq);
+ return -1;
+ }
+ /* I/O address should be between 0x100 and 0x3ff and should be
+ * multiple of 8. */
+ if (base < 0x100 || base > 0x3ff || base & 0x7) {
+ printk (KERN_INFO "cosa_probe: invalid I/O address 0x%x\n",
+ base);
+ return -1;
+ }
+ /* DMA should be 0,1 or 3-7 */
+ if (dma < 0 || dma == 4 || dma > 7) {
+ printk (KERN_INFO "cosa_probe: invalid DMA %d\n", dma);
+ return -1;
+ }
+ /* and finally, on 16-bit COSA DMA should be 4-7 and
+ * I/O base should not be multiple of 0x10 */
+ if (((base & 0x8) && dma < 4) || (!(base & 0x8) && dma > 3)) {
+ printk (KERN_INFO "cosa_probe: 8/16 bit base and DMA mismatch"
+ " (base=0x%x, dma=%d)\n", base, dma);
+ return -1;
+ }
+
+ cosa->dma = dma;
+ cosa->datareg = base;
+ cosa->statusreg = is_8bit(cosa)?base+1:base+2;
+ spin_lock_init(&cosa->lock);
+
+ if (!request_region(base, is_8bit(cosa)?2:4,"cosa"))
+ return -1;
+
+ if (cosa_reset_and_read_id(cosa, cosa->id_string) < 0) {
+ printk(KERN_DEBUG "cosa: probe at 0x%x failed.\n", base);
+ err = -1;
+ goto err_out;
+ }
+
+ /* Test the validity of identification string */
+ if (!strncmp(cosa->id_string, "SRP", 3))
+ cosa->type = "srp";
+ else if (!strncmp(cosa->id_string, "COSA", 4))
+ cosa->type = is_8bit(cosa)? "cosa8": "cosa16";
+ else {
+/* Print a warning only if we are not autoprobing */
+#ifndef COSA_ISA_AUTOPROBE
+ printk(KERN_INFO "cosa: valid signature not found at 0x%x.\n",
+ base);
+#endif
+ err = -1;
+ goto err_out;
+ }
+ /* Update the name of the region now we know the type of card */
+ release_region(base, is_8bit(cosa)?2:4);
+ if (!request_region(base, is_8bit(cosa)?2:4, cosa->type)) {
+ printk(KERN_DEBUG "cosa: changing name at 0x%x failed.\n", base);
+ return -1;
+ }
+
+ /* Now do IRQ autoprobe */
+ if (irq < 0) {
+ unsigned long irqs;
+/* printk(KERN_INFO "IRQ autoprobe\n"); */
+ irqs = probe_irq_on();
+ /*
+ * Enable interrupt on tx buffer empty (it sure is)
+ * really sure ?
+ * FIXME: When this code is not used as module, we should
+ * probably call udelay() instead of the interruptible sleep.
+ */
+ set_current_state(TASK_INTERRUPTIBLE);
+ cosa_putstatus(cosa, SR_TX_INT_ENA);
+ schedule_timeout(30);
+ irq = probe_irq_off(irqs);
+ /* Disable all IRQs from the card */
+ cosa_putstatus(cosa, 0);
+ /* Empty the received data register */
+ cosa_getdata8(cosa);
+
+ if (irq < 0) {
+ printk (KERN_INFO "cosa IRQ autoprobe: multiple interrupts obtained (%d, board at 0x%x)\n",
+ irq, cosa->datareg);
+ err = -1;
+ goto err_out;
+ }
+ if (irq == 0) {
+ printk (KERN_INFO "cosa IRQ autoprobe: no interrupt obtained (board at 0x%x)\n",
+ cosa->datareg);
+ /* return -1; */
+ }
+ }
+
+ cosa->irq = irq;
+ cosa->num = nr_cards;
+ cosa->usage = 0;
+ cosa->nchannels = 2; /* FIXME: how to determine this? */
+
+ if (request_irq(cosa->irq, cosa_interrupt, 0, cosa->type, cosa)) {
+ err = -1;
+ goto err_out;
+ }
+ if (request_dma(cosa->dma, cosa->type)) {
+ err = -1;
+ goto err_out1;
+ }
+
+ cosa->bouncebuf = kmalloc(COSA_MTU, GFP_KERNEL|GFP_DMA);
+ if (!cosa->bouncebuf) {
+ err = -ENOMEM;
+ goto err_out2;
+ }
+ sprintf(cosa->name, "cosa%d", cosa->num);
+
+ /* Initialize the per-channel data */
+ cosa->chan = kmalloc(sizeof(struct channel_data)*cosa->nchannels,
+ GFP_KERNEL);
+ if (!cosa->chan) {
+ err = -ENOMEM;
+ goto err_out3;
+ }
+ memset(cosa->chan, 0, sizeof(struct channel_data)*cosa->nchannels);
+ for (i=0; i<cosa->nchannels; i++) {
+ cosa->chan[i].cosa = cosa;
+ cosa->chan[i].num = i;
+ channel_init(cosa->chan+i);
+ }
+
+ printk (KERN_INFO "cosa%d: %s (%s at 0x%x irq %d dma %d), %d channels\n",
+ cosa->num, cosa->id_string, cosa->type,
+ cosa->datareg, cosa->irq, cosa->dma, cosa->nchannels);
+
+ return nr_cards++;
+err_out3:
+ kfree(cosa->bouncebuf);
+err_out2:
+ free_dma(cosa->dma);
+err_out1:
+ free_irq(cosa->irq, cosa);
+err_out:
+ release_region(cosa->datareg,is_8bit(cosa)?2:4);
+ printk(KERN_NOTICE "cosa%d: allocating resources failed\n",
+ cosa->num);
+ return err;
+}
+
+
+/*---------- SPPP/HDLC netdevice ---------- */
+
+static void cosa_setup(struct net_device *d)
+{
+ d->open = cosa_sppp_open;
+ d->stop = cosa_sppp_close;
+ d->hard_start_xmit = cosa_sppp_tx;
+ d->do_ioctl = cosa_sppp_ioctl;
+ d->get_stats = cosa_net_stats;
+ d->tx_timeout = cosa_sppp_timeout;
+ d->watchdog_timeo = TX_TIMEOUT;
+}
+
+static void sppp_channel_init(struct channel_data *chan)
+{
+ struct net_device *d;
+ chan->if_ptr = &chan->pppdev;
+ d = alloc_netdev(0, chan->name, cosa_setup);
+ if (!d) {
+ printk(KERN_WARNING "%s: alloc_netdev failed.\n", chan->name);
+ return;
+ }
+ chan->pppdev.dev = d;
+ d->base_addr = chan->cosa->datareg;
+ d->irq = chan->cosa->irq;
+ d->dma = chan->cosa->dma;
+ d->priv = chan;
+ sppp_attach(&chan->pppdev);
+ if (register_netdev(d)) {
+ printk(KERN_WARNING "%s: register_netdev failed.\n", d->name);
+ sppp_detach(d);
+ free_netdev(d);
+ chan->pppdev.dev = NULL;
+ return;
+ }
+}
+
+static void sppp_channel_delete(struct channel_data *chan)
+{
+ unregister_netdev(chan->pppdev.dev);
+ sppp_detach(chan->pppdev.dev);
+ free_netdev(chan->pppdev.dev);
+ chan->pppdev.dev = NULL;
+}
+
+static int cosa_sppp_open(struct net_device *d)
+{
+ struct channel_data *chan = d->priv;
+ int err;
+ unsigned long flags;
+
+ if (!(chan->cosa->firmware_status & COSA_FW_START)) {
+ printk(KERN_NOTICE "%s: start the firmware first (status %d)\n",
+ chan->cosa->name, chan->cosa->firmware_status);
+ return -EPERM;
+ }
+ spin_lock_irqsave(&chan->cosa->lock, flags);
+ if (chan->usage != 0) {
+ printk(KERN_WARNING "%s: sppp_open called with usage count %d\n",
+ chan->name, chan->usage);
+ spin_unlock_irqrestore(&chan->cosa->lock, flags);
+ return -EBUSY;
+ }
+ chan->setup_rx = sppp_setup_rx;
+ chan->tx_done = sppp_tx_done;
+ chan->rx_done = sppp_rx_done;
+ chan->usage=-1;
+ chan->cosa->usage++;
+ spin_unlock_irqrestore(&chan->cosa->lock, flags);
+
+ err = sppp_open(d);
+ if (err) {
+ spin_lock_irqsave(&chan->cosa->lock, flags);
+ chan->usage=0;
+ chan->cosa->usage--;
+
+ spin_unlock_irqrestore(&chan->cosa->lock, flags);
+ return err;
+ }
+
+ netif_start_queue(d);
+ cosa_enable_rx(chan);
+ return 0;
+}
+
+static int cosa_sppp_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct channel_data *chan = dev->priv;
+
+ netif_stop_queue(dev);
+
+ chan->tx_skb = skb;
+ cosa_start_tx(chan, skb->data, skb->len);
+ return 0;
+}
+
+static void cosa_sppp_timeout(struct net_device *dev)
+{
+ struct channel_data *chan = dev->priv;
+
+ if (test_bit(RXBIT, &chan->cosa->rxtx)) {
+ chan->stats.rx_errors++;
+ chan->stats.rx_missed_errors++;
+ } else {
+ chan->stats.tx_errors++;
+ chan->stats.tx_aborted_errors++;
+ }
+ cosa_kick(chan->cosa);
+ if (chan->tx_skb) {
+ dev_kfree_skb(chan->tx_skb);
+ chan->tx_skb = NULL;
+ }
+ netif_wake_queue(dev);
+}
+
+static int cosa_sppp_close(struct net_device *d)
+{
+ struct channel_data *chan = d->priv;
+ unsigned long flags;
+
+ netif_stop_queue(d);
+ sppp_close(d);
+ cosa_disable_rx(chan);
+ spin_lock_irqsave(&chan->cosa->lock, flags);
+ if (chan->rx_skb) {
+ kfree_skb(chan->rx_skb);
+ chan->rx_skb = NULL;
+ }
+ if (chan->tx_skb) {
+ kfree_skb(chan->tx_skb);
+ chan->tx_skb = NULL;
+ }
+ chan->usage=0;
+ chan->cosa->usage--;
+ spin_unlock_irqrestore(&chan->cosa->lock, flags);
+ return 0;
+}
+
+static char *sppp_setup_rx(struct channel_data *chan, int size)
+{
+ /*
+ * We can safely fall back to non-dma-able memory, because we have
+ * the cosa->bouncebuf pre-allocated.
+ */
+ if (chan->rx_skb)
+ kfree_skb(chan->rx_skb);
+ chan->rx_skb = dev_alloc_skb(size);
+ if (chan->rx_skb == NULL) {
+ printk(KERN_NOTICE "%s: Memory squeeze, dropping packet\n",
+ chan->name);
+ chan->stats.rx_dropped++;
+ return NULL;
+ }
+ chan->pppdev.dev->trans_start = jiffies;
+ return skb_put(chan->rx_skb, size);
+}
+
+static int sppp_rx_done(struct channel_data *chan)
+{
+ if (!chan->rx_skb) {
+ printk(KERN_WARNING "%s: rx_done with empty skb!\n",
+ chan->name);
+ chan->stats.rx_errors++;
+ chan->stats.rx_frame_errors++;
+ return 0;
+ }
+ chan->rx_skb->protocol = htons(ETH_P_WAN_PPP);
+ chan->rx_skb->dev = chan->pppdev.dev;
+ chan->rx_skb->mac.raw = chan->rx_skb->data;
+ chan->stats.rx_packets++;
+ chan->stats.rx_bytes += chan->cosa->rxsize;
+ netif_rx(chan->rx_skb);
+ chan->rx_skb = NULL;
+ chan->pppdev.dev->last_rx = jiffies;
+ return 0;
+}
+
+/* ARGSUSED */
+static int sppp_tx_done(struct channel_data *chan, int size)
+{
+ if (!chan->tx_skb) {
+ printk(KERN_WARNING "%s: tx_done with empty skb!\n",
+ chan->name);
+ chan->stats.tx_errors++;
+ chan->stats.tx_aborted_errors++;
+ return 1;
+ }
+ dev_kfree_skb_irq(chan->tx_skb);
+ chan->tx_skb = NULL;
+ chan->stats.tx_packets++;
+ chan->stats.tx_bytes += size;
+ netif_wake_queue(chan->pppdev.dev);
+ return 1;
+}
+
+static struct net_device_stats *cosa_net_stats(struct net_device *dev)
+{
+ struct channel_data *chan = dev->priv;
+ return &chan->stats;
+}
+
+
+/*---------- Character device ---------- */
+
+static void chardev_channel_init(struct channel_data *chan)
+{
+ init_MUTEX(&chan->rsem);
+ init_MUTEX(&chan->wsem);
+}
+
+static ssize_t cosa_read(struct file *file,
+ char __user *buf, size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+ struct channel_data *chan = file->private_data;
+ struct cosa_data *cosa = chan->cosa;
+ char *kbuf;
+
+ if (!(cosa->firmware_status & COSA_FW_START)) {
+ printk(KERN_NOTICE "%s: start the firmware first (status %d)\n",
+ cosa->name, cosa->firmware_status);
+ return -EPERM;
+ }
+ if (down_interruptible(&chan->rsem))
+ return -ERESTARTSYS;
+
+ if ((chan->rxdata = kmalloc(COSA_MTU, GFP_DMA|GFP_KERNEL)) == NULL) {
+ printk(KERN_INFO "%s: cosa_read() - OOM\n", cosa->name);
+ up(&chan->rsem);
+ return -ENOMEM;
+ }
+
+ chan->rx_status = 0;
+ cosa_enable_rx(chan);
+ spin_lock_irqsave(&cosa->lock, flags);
+ add_wait_queue(&chan->rxwaitq, &wait);
+ while(!chan->rx_status) {
+ current->state = TASK_INTERRUPTIBLE;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ schedule();
+ spin_lock_irqsave(&cosa->lock, flags);
+ if (signal_pending(current) && chan->rx_status == 0) {
+ chan->rx_status = 1;
+ remove_wait_queue(&chan->rxwaitq, &wait);
+ current->state = TASK_RUNNING;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ up(&chan->rsem);
+ return -ERESTARTSYS;
+ }
+ }
+ remove_wait_queue(&chan->rxwaitq, &wait);
+ current->state = TASK_RUNNING;
+ kbuf = chan->rxdata;
+ count = chan->rxsize;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ up(&chan->rsem);
+
+ if (copy_to_user(buf, kbuf, count)) {
+ kfree(kbuf);
+ return -EFAULT;
+ }
+ kfree(kbuf);
+ return count;
+}
+
+static char *chrdev_setup_rx(struct channel_data *chan, int size)
+{
+ /* Expect size <= COSA_MTU */
+ chan->rxsize = size;
+ return chan->rxdata;
+}
+
+static int chrdev_rx_done(struct channel_data *chan)
+{
+ if (chan->rx_status) { /* Reader has died */
+ kfree(chan->rxdata);
+ up(&chan->wsem);
+ }
+ chan->rx_status = 1;
+ wake_up_interruptible(&chan->rxwaitq);
+ return 1;
+}
+
+
+static ssize_t cosa_write(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct channel_data *chan = file->private_data;
+ struct cosa_data *cosa = chan->cosa;
+ unsigned long flags;
+ char *kbuf;
+
+ if (!(cosa->firmware_status & COSA_FW_START)) {
+ printk(KERN_NOTICE "%s: start the firmware first (status %d)\n",
+ cosa->name, cosa->firmware_status);
+ return -EPERM;
+ }
+ if (down_interruptible(&chan->wsem))
+ return -ERESTARTSYS;
+
+ if (count > COSA_MTU)
+ count = COSA_MTU;
+
+ /* Allocate the buffer */
+ if ((kbuf = kmalloc(count, GFP_KERNEL|GFP_DMA)) == NULL) {
+ printk(KERN_NOTICE "%s: cosa_write() OOM - dropping packet\n",
+ cosa->name);
+ up(&chan->wsem);
+ return -ENOMEM;
+ }
+ if (copy_from_user(kbuf, buf, count)) {
+ up(&chan->wsem);
+ kfree(kbuf);
+ return -EFAULT;
+ }
+ chan->tx_status=0;
+ cosa_start_tx(chan, kbuf, count);
+
+ spin_lock_irqsave(&cosa->lock, flags);
+ add_wait_queue(&chan->txwaitq, &wait);
+ while(!chan->tx_status) {
+ current->state = TASK_INTERRUPTIBLE;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ schedule();
+ spin_lock_irqsave(&cosa->lock, flags);
+ if (signal_pending(current) && chan->tx_status == 0) {
+ chan->tx_status = 1;
+ remove_wait_queue(&chan->txwaitq, &wait);
+ current->state = TASK_RUNNING;
+ chan->tx_status = 1;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return -ERESTARTSYS;
+ }
+ }
+ remove_wait_queue(&chan->txwaitq, &wait);
+ current->state = TASK_RUNNING;
+ up(&chan->wsem);
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ kfree(kbuf);
+ return count;
+}
+
+static int chrdev_tx_done(struct channel_data *chan, int size)
+{
+ if (chan->tx_status) { /* Writer was interrupted */
+ kfree(chan->txbuf);
+ up(&chan->wsem);
+ }
+ chan->tx_status = 1;
+ wake_up_interruptible(&chan->txwaitq);
+ return 1;
+}
+
+static unsigned int cosa_poll(struct file *file, poll_table *poll)
+{
+ printk(KERN_INFO "cosa_poll is here\n");
+ return 0;
+}
+
+static int cosa_open(struct inode *inode, struct file *file)
+{
+ struct cosa_data *cosa;
+ struct channel_data *chan;
+ unsigned long flags;
+ int n;
+
+ if ((n=iminor(file->f_dentry->d_inode)>>CARD_MINOR_BITS)
+ >= nr_cards)
+ return -ENODEV;
+ cosa = cosa_cards+n;
+
+ if ((n=iminor(file->f_dentry->d_inode)
+ & ((1<<CARD_MINOR_BITS)-1)) >= cosa->nchannels)
+ return -ENODEV;
+ chan = cosa->chan + n;
+
+ file->private_data = chan;
+
+ spin_lock_irqsave(&cosa->lock, flags);
+
+ if (chan->usage < 0) { /* in netdev mode */
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return -EBUSY;
+ }
+ cosa->usage++;
+ chan->usage++;
+
+ chan->tx_done = chrdev_tx_done;
+ chan->setup_rx = chrdev_setup_rx;
+ chan->rx_done = chrdev_rx_done;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return 0;
+}
+
+static int cosa_release(struct inode *inode, struct file *file)
+{
+ struct channel_data *channel = file->private_data;
+ struct cosa_data *cosa;
+ unsigned long flags;
+
+ cosa = channel->cosa;
+ spin_lock_irqsave(&cosa->lock, flags);
+ cosa->usage--;
+ channel->usage--;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return 0;
+}
+
+#ifdef COSA_FASYNC_WORKING
+static struct fasync_struct *fasync[256] = { NULL, };
+
+/* To be done ... */
+static int cosa_fasync(struct inode *inode, struct file *file, int on)
+{
+ int port = iminor(inode);
+ int rv = fasync_helper(inode, file, on, &fasync[port]);
+ return rv < 0 ? rv : 0;
+}
+#endif
+
+
+/* ---------- Ioctls ---------- */
+
+/*
+ * Ioctl subroutines can safely be made inline, because they are called
+ * only from cosa_ioctl().
+ */
+static inline int cosa_reset(struct cosa_data *cosa)
+{
+ char idstring[COSA_MAX_ID_STRING];
+ if (cosa->usage > 1)
+ printk(KERN_INFO "cosa%d: WARNING: reset requested with cosa->usage > 1 (%d). Odd things may happen.\n",
+ cosa->num, cosa->usage);
+ cosa->firmware_status &= ~(COSA_FW_RESET|COSA_FW_START);
+ if (cosa_reset_and_read_id(cosa, idstring) < 0) {
+ printk(KERN_NOTICE "cosa%d: reset failed\n", cosa->num);
+ return -EIO;
+ }
+ printk(KERN_INFO "cosa%d: resetting device: %s\n", cosa->num,
+ idstring);
+ cosa->firmware_status |= COSA_FW_RESET;
+ return 0;
+}
+
+/* High-level function to download data into COSA memory. Calls download() */
+static inline int cosa_download(struct cosa_data *cosa, void __user *arg)
+{
+ struct cosa_download d;
+ int i;
+
+ if (cosa->usage > 1)
+ printk(KERN_INFO "%s: WARNING: download of microcode requested with cosa->usage > 1 (%d). Odd things may happen.\n",
+ cosa->name, cosa->usage);
+ if (!(cosa->firmware_status & COSA_FW_RESET)) {
+ printk(KERN_NOTICE "%s: reset the card first (status %d).\n",
+ cosa->name, cosa->firmware_status);
+ return -EPERM;
+ }
+
+ if (copy_from_user(&d, arg, sizeof(d)))
+ return -EFAULT;
+
+ if (d.addr < 0 || d.addr > COSA_MAX_FIRMWARE_SIZE)
+ return -EINVAL;
+ if (d.len < 0 || d.len > COSA_MAX_FIRMWARE_SIZE)
+ return -EINVAL;
+
+
+ /* If something fails, force the user to reset the card */
+ cosa->firmware_status &= ~(COSA_FW_RESET|COSA_FW_DOWNLOAD);
+
+ i = download(cosa, d.code, d.len, d.addr);
+ if (i < 0) {
+ printk(KERN_NOTICE "cosa%d: microcode download failed: %d\n",
+ cosa->num, i);
+ return -EIO;
+ }
+ printk(KERN_INFO "cosa%d: downloading microcode - 0x%04x bytes at 0x%04x\n",
+ cosa->num, d.len, d.addr);
+ cosa->firmware_status |= COSA_FW_RESET|COSA_FW_DOWNLOAD;
+ return 0;
+}
+
+/* High-level function to read COSA memory. Calls readmem() */
+static inline int cosa_readmem(struct cosa_data *cosa, void __user *arg)
+{
+ struct cosa_download d;
+ int i;
+
+ if (cosa->usage > 1)
+ printk(KERN_INFO "cosa%d: WARNING: readmem requested with "
+ "cosa->usage > 1 (%d). Odd things may happen.\n",
+ cosa->num, cosa->usage);
+ if (!(cosa->firmware_status & COSA_FW_RESET)) {
+ printk(KERN_NOTICE "%s: reset the card first (status %d).\n",
+ cosa->name, cosa->firmware_status);
+ return -EPERM;
+ }
+
+ if (copy_from_user(&d, arg, sizeof(d)))
+ return -EFAULT;
+
+ /* If something fails, force the user to reset the card */
+ cosa->firmware_status &= ~COSA_FW_RESET;
+
+ i = readmem(cosa, d.code, d.len, d.addr);
+ if (i < 0) {
+ printk(KERN_NOTICE "cosa%d: reading memory failed: %d\n",
+ cosa->num, i);
+ return -EIO;
+ }
+ printk(KERN_INFO "cosa%d: reading card memory - 0x%04x bytes at 0x%04x\n",
+ cosa->num, d.len, d.addr);
+ cosa->firmware_status |= COSA_FW_RESET;
+ return 0;
+}
+
+/* High-level function to start microcode. Calls startmicrocode(). */
+static inline int cosa_start(struct cosa_data *cosa, int address)
+{
+ int i;
+
+ if (cosa->usage > 1)
+ printk(KERN_INFO "cosa%d: WARNING: start microcode requested with cosa->usage > 1 (%d). Odd things may happen.\n",
+ cosa->num, cosa->usage);
+
+ if ((cosa->firmware_status & (COSA_FW_RESET|COSA_FW_DOWNLOAD))
+ != (COSA_FW_RESET|COSA_FW_DOWNLOAD)) {
+ printk(KERN_NOTICE "%s: download the microcode and/or reset the card first (status %d).\n",
+ cosa->name, cosa->firmware_status);
+ return -EPERM;
+ }
+ cosa->firmware_status &= ~COSA_FW_RESET;
+ if ((i=startmicrocode(cosa, address)) < 0) {
+ printk(KERN_NOTICE "cosa%d: start microcode at 0x%04x failed: %d\n",
+ cosa->num, address, i);
+ return -EIO;
+ }
+ printk(KERN_INFO "cosa%d: starting microcode at 0x%04x\n",
+ cosa->num, address);
+ cosa->startaddr = address;
+ cosa->firmware_status |= COSA_FW_START;
+ return 0;
+}
+
+/* Buffer of size at least COSA_MAX_ID_STRING is expected */
+static inline int cosa_getidstr(struct cosa_data *cosa, char __user *string)
+{
+ int l = strlen(cosa->id_string)+1;
+ if (copy_to_user(string, cosa->id_string, l))
+ return -EFAULT;
+ return l;
+}
+
+/* Buffer of size at least COSA_MAX_ID_STRING is expected */
+static inline int cosa_gettype(struct cosa_data *cosa, char __user *string)
+{
+ int l = strlen(cosa->type)+1;
+ if (copy_to_user(string, cosa->type, l))
+ return -EFAULT;
+ return l;
+}
+
+static int cosa_ioctl_common(struct cosa_data *cosa,
+ struct channel_data *channel, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ switch(cmd) {
+ case COSAIORSET: /* Reset the device */
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ return cosa_reset(cosa);
+ case COSAIOSTRT: /* Start the firmware */
+ if (!capable(CAP_SYS_RAWIO))
+ return -EACCES;
+ return cosa_start(cosa, arg);
+ case COSAIODOWNLD: /* Download the firmware */
+ if (!capable(CAP_SYS_RAWIO))
+ return -EACCES;
+
+ return cosa_download(cosa, argp);
+ case COSAIORMEM:
+ if (!capable(CAP_SYS_RAWIO))
+ return -EACCES;
+ return cosa_readmem(cosa, argp);
+ case COSAIORTYPE:
+ return cosa_gettype(cosa, argp);
+ case COSAIORIDSTR:
+ return cosa_getidstr(cosa, argp);
+ case COSAIONRCARDS:
+ return nr_cards;
+ case COSAIONRCHANS:
+ return cosa->nchannels;
+ case COSAIOBMSET:
+ if (!capable(CAP_SYS_RAWIO))
+ return -EACCES;
+ if (is_8bit(cosa))
+ return -EINVAL;
+ if (arg != COSA_BM_OFF && arg != COSA_BM_ON)
+ return -EINVAL;
+ cosa->busmaster = arg;
+ return 0;
+ case COSAIOBMGET:
+ return cosa->busmaster;
+ }
+ return -ENOIOCTLCMD;
+}
+
+static int cosa_sppp_ioctl(struct net_device *dev, struct ifreq *ifr,
+ int cmd)
+{
+ int rv;
+ struct channel_data *chan = dev->priv;
+ rv = cosa_ioctl_common(chan->cosa, chan, cmd, (unsigned long)ifr->ifr_data);
+ if (rv == -ENOIOCTLCMD) {
+ return sppp_do_ioctl(dev, ifr, cmd);
+ }
+ return rv;
+}
+
+static int cosa_chardev_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct channel_data *channel = file->private_data;
+ struct cosa_data *cosa = channel->cosa;
+ return cosa_ioctl_common(cosa, channel, cmd, arg);
+}
+
+
+/*---------- HW layer interface ---------- */
+
+/*
+ * The higher layer can bind itself to the HW layer by setting the callbacks
+ * in the channel_data structure and by using these routines.
+ */
+static void cosa_enable_rx(struct channel_data *chan)
+{
+ struct cosa_data *cosa = chan->cosa;
+
+ if (!test_and_set_bit(chan->num, &cosa->rxbitmap))
+ put_driver_status(cosa);
+}
+
+static void cosa_disable_rx(struct channel_data *chan)
+{
+ struct cosa_data *cosa = chan->cosa;
+
+ if (test_and_clear_bit(chan->num, &cosa->rxbitmap))
+ put_driver_status(cosa);
+}
+
+/*
+ * FIXME: This routine probably should check for cosa_start_tx() called when
+ * the previous transmit is still unfinished. In this case the non-zero
+ * return value should indicate to the caller that the queuing(sp?) up
+ * the transmit has failed.
+ */
+static int cosa_start_tx(struct channel_data *chan, char *buf, int len)
+{
+ struct cosa_data *cosa = chan->cosa;
+ unsigned long flags;
+#ifdef DEBUG_DATA
+ int i;
+
+ printk(KERN_INFO "cosa%dc%d: starting tx(0x%x)", chan->cosa->num,
+ chan->num, len);
+ for (i=0; i<len; i++)
+ printk(" %02x", buf[i]&0xff);
+ printk("\n");
+#endif
+ spin_lock_irqsave(&cosa->lock, flags);
+ chan->txbuf = buf;
+ chan->txsize = len;
+ if (len > COSA_MTU)
+ chan->txsize = COSA_MTU;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+
+ /* Tell the firmware we are ready */
+ set_bit(chan->num, &cosa->txbitmap);
+ put_driver_status(cosa);
+
+ return 0;
+}
+
+static void put_driver_status(struct cosa_data *cosa)
+{
+ unsigned long flags;
+ int status;
+
+ spin_lock_irqsave(&cosa->lock, flags);
+
+ status = (cosa->rxbitmap ? DRIVER_RX_READY : 0)
+ | (cosa->txbitmap ? DRIVER_TX_READY : 0)
+ | (cosa->txbitmap? ~(cosa->txbitmap<<DRIVER_TXMAP_SHIFT)
+ &DRIVER_TXMAP_MASK : 0);
+ if (!cosa->rxtx) {
+ if (cosa->rxbitmap|cosa->txbitmap) {
+ if (!cosa->enabled) {
+ cosa_putstatus(cosa, SR_RX_INT_ENA);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, SR_RX_INT_ENA);
+#endif
+ cosa->enabled = 1;
+ }
+ } else if (cosa->enabled) {
+ cosa->enabled = 0;
+ cosa_putstatus(cosa, 0);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, 0);
+#endif
+ }
+ cosa_putdata8(cosa, status);
+#ifdef DEBUG_IO
+ debug_data_cmd(cosa, status);
+#endif
+ }
+ spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static void put_driver_status_nolock(struct cosa_data *cosa)
+{
+ int status;
+
+ status = (cosa->rxbitmap ? DRIVER_RX_READY : 0)
+ | (cosa->txbitmap ? DRIVER_TX_READY : 0)
+ | (cosa->txbitmap? ~(cosa->txbitmap<<DRIVER_TXMAP_SHIFT)
+ &DRIVER_TXMAP_MASK : 0);
+
+ if (cosa->rxbitmap|cosa->txbitmap) {
+ cosa_putstatus(cosa, SR_RX_INT_ENA);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, SR_RX_INT_ENA);
+#endif
+ cosa->enabled = 1;
+ } else {
+ cosa_putstatus(cosa, 0);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, 0);
+#endif
+ cosa->enabled = 0;
+ }
+ cosa_putdata8(cosa, status);
+#ifdef DEBUG_IO
+ debug_data_cmd(cosa, status);
+#endif
+}
+
+/*
+ * The "kickme" function: When the DMA times out, this is called to
+ * clean up the driver status.
+ * FIXME: Preliminary support, the interface is probably wrong.
+ */
+static void cosa_kick(struct cosa_data *cosa)
+{
+ unsigned long flags, flags1;
+ char *s = "(probably) IRQ";
+
+ if (test_bit(RXBIT, &cosa->rxtx))
+ s = "RX DMA";
+ if (test_bit(TXBIT, &cosa->rxtx))
+ s = "TX DMA";
+
+ printk(KERN_INFO "%s: %s timeout - restarting.\n", cosa->name, s);
+ spin_lock_irqsave(&cosa->lock, flags);
+ cosa->rxtx = 0;
+
+ flags1 = claim_dma_lock();
+ disable_dma(cosa->dma);
+ clear_dma_ff(cosa->dma);
+ release_dma_lock(flags1);
+
+ /* FIXME: Anything else? */
+ udelay(100);
+ cosa_putstatus(cosa, 0);
+ udelay(100);
+ (void) cosa_getdata8(cosa);
+ udelay(100);
+ cosa_putdata8(cosa, 0);
+ udelay(100);
+ put_driver_status_nolock(cosa);
+ spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+/*
+ * Check if the whole buffer is DMA-able. It means it is below the 16M of
+ * physical memory and doesn't span the 64k boundary. For now it seems
+ * SKB's never do this, but we'll check this anyway.
+ */
+static int cosa_dma_able(struct channel_data *chan, char *buf, int len)
+{
+ static int count;
+ unsigned long b = (unsigned long)buf;
+ if (b+len >= MAX_DMA_ADDRESS)
+ return 0;
+ if ((b^ (b+len)) & 0x10000) {
+ if (count++ < 5)
+ printk(KERN_INFO "%s: packet spanning a 64k boundary\n",
+ chan->name);
+ return 0;
+ }
+ return 1;
+}
+
+
+/* ---------- The SRP/COSA ROM monitor functions ---------- */
+
+/*
+ * Downloading SRP microcode: say "w" to SRP monitor, it answers by "w=",
+ * drivers need to say 4-digit hex number meaning start address of the microcode
+ * separated by a single space. Monitor replies by saying " =". Now driver
+ * has to write 4-digit hex number meaning the last byte address ended
+ * by a single space. Monitor has to reply with a space. Now the download
+ * begins. After the download monitor replies with "\r\n." (CR LF dot).
+ */
+static int download(struct cosa_data *cosa, const char __user *microcode, int length, int address)
+{
+ int i;
+
+ if (put_wait_data(cosa, 'w') == -1) return -1;
+ if ((i=get_wait_data(cosa)) != 'w') { printk("dnld: 0x%04x\n",i); return -2;}
+ if (get_wait_data(cosa) != '=') return -3;
+
+ if (puthexnumber(cosa, address) < 0) return -4;
+ if (put_wait_data(cosa, ' ') == -1) return -10;
+ if (get_wait_data(cosa) != ' ') return -11;
+ if (get_wait_data(cosa) != '=') return -12;
+
+ if (puthexnumber(cosa, address+length-1) < 0) return -13;
+ if (put_wait_data(cosa, ' ') == -1) return -18;
+ if (get_wait_data(cosa) != ' ') return -19;
+
+ while (length--) {
+ char c;
+#ifndef SRP_DOWNLOAD_AT_BOOT
+ if (get_user(c, microcode))
+ return -23; /* ??? */
+#else
+ c = *microcode;
+#endif
+ if (put_wait_data(cosa, c) == -1)
+ return -20;
+ microcode++;
+ }
+
+ if (get_wait_data(cosa) != '\r') return -21;
+ if (get_wait_data(cosa) != '\n') return -22;
+ if (get_wait_data(cosa) != '.') return -23;
+#if 0
+ printk(KERN_DEBUG "cosa%d: download completed.\n", cosa->num);
+#endif
+ return 0;
+}
+
+
+/*
+ * Starting microcode is done via the "g" command of the SRP monitor.
+ * The chat should be the following: "g" "g=" "<addr><CR>"
+ * "<CR><CR><LF><CR><LF>".
+ */
+static int startmicrocode(struct cosa_data *cosa, int address)
+{
+ if (put_wait_data(cosa, 'g') == -1) return -1;
+ if (get_wait_data(cosa) != 'g') return -2;
+ if (get_wait_data(cosa) != '=') return -3;
+
+ if (puthexnumber(cosa, address) < 0) return -4;
+ if (put_wait_data(cosa, '\r') == -1) return -5;
+
+ if (get_wait_data(cosa) != '\r') return -6;
+ if (get_wait_data(cosa) != '\r') return -7;
+ if (get_wait_data(cosa) != '\n') return -8;
+ if (get_wait_data(cosa) != '\r') return -9;
+ if (get_wait_data(cosa) != '\n') return -10;
+#if 0
+ printk(KERN_DEBUG "cosa%d: microcode started\n", cosa->num);
+#endif
+ return 0;
+}
+
+/*
+ * Reading memory is done via the "r" command of the SRP monitor.
+ * The chat is the following "r" "r=" "<addr> " " =" "<last_byte> " " "
+ * Then driver can read the data and the conversation is finished
+ * by SRP monitor sending "<CR><LF>." (dot at the end).
+ *
+ * This routine is not needed during the normal operation and serves
+ * for debugging purposes only.
+ */
+static int readmem(struct cosa_data *cosa, char __user *microcode, int length, int address)
+{
+ if (put_wait_data(cosa, 'r') == -1) return -1;
+ if ((get_wait_data(cosa)) != 'r') return -2;
+ if ((get_wait_data(cosa)) != '=') return -3;
+
+ if (puthexnumber(cosa, address) < 0) return -4;
+ if (put_wait_data(cosa, ' ') == -1) return -5;
+ if (get_wait_data(cosa) != ' ') return -6;
+ if (get_wait_data(cosa) != '=') return -7;
+
+ if (puthexnumber(cosa, address+length-1) < 0) return -8;
+ if (put_wait_data(cosa, ' ') == -1) return -9;
+ if (get_wait_data(cosa) != ' ') return -10;
+
+ while (length--) {
+ char c;
+ int i;
+ if ((i=get_wait_data(cosa)) == -1) {
+ printk (KERN_INFO "cosa: 0x%04x bytes remaining\n",
+ length);
+ return -11;
+ }
+ c=i;
+#if 1
+ if (put_user(c, microcode))
+ return -23; /* ??? */
+#else
+ *microcode = c;
+#endif
+ microcode++;
+ }
+
+ if (get_wait_data(cosa) != '\r') return -21;
+ if (get_wait_data(cosa) != '\n') return -22;
+ if (get_wait_data(cosa) != '.') return -23;
+#if 0
+ printk(KERN_DEBUG "cosa%d: readmem completed.\n", cosa->num);
+#endif
+ return 0;
+}
+
+/*
+ * This function resets the device and reads the initial prompt
+ * of the device's ROM monitor.
+ */
+static int cosa_reset_and_read_id(struct cosa_data *cosa, char *idstring)
+{
+ int i=0, id=0, prev=0, curr=0;
+
+ /* Reset the card ... */
+ cosa_putstatus(cosa, 0);
+ cosa_getdata8(cosa);
+ cosa_putstatus(cosa, SR_RST);
+#ifdef MODULE
+ msleep(500);
+#else
+ udelay(5*100000);
+#endif
+ /* Disable all IRQs from the card */
+ cosa_putstatus(cosa, 0);
+
+ /*
+ * Try to read the ID string. The card then prints out the
+ * identification string ended by the "\n\x2e".
+ *
+ * The following loop is indexed through i (instead of id)
+ * to avoid looping forever when for any reason
+ * the port returns '\r', '\n' or '\x2e' permanently.
+ */
+ for (i=0; i<COSA_MAX_ID_STRING-1; i++, prev=curr) {
+ if ((curr = get_wait_data(cosa)) == -1) {
+ return -1;
+ }
+ curr &= 0xff;
+ if (curr != '\r' && curr != '\n' && curr != 0x2e)
+ idstring[id++] = curr;
+ if (curr == 0x2e && prev == '\n')
+ break;
+ }
+ /* Perhaps we should fail when i==COSA_MAX_ID_STRING-1 ? */
+ idstring[id] = '\0';
+ return id;
+}
+
+
+/* ---------- Auxiliary routines for COSA/SRP monitor ---------- */
+
+/*
+ * This routine gets the data byte from the card waiting for the SR_RX_RDY
+ * bit to be set in a loop. It should be used in the exceptional cases
+ * only (for example when resetting the card or downloading the firmware.
+ */
+static int get_wait_data(struct cosa_data *cosa)
+{
+ int retries = 1000;
+
+ while (--retries) {
+ /* read data and return them */
+ if (cosa_getstatus(cosa) & SR_RX_RDY) {
+ short r;
+ r = cosa_getdata8(cosa);
+#if 0
+ printk(KERN_INFO "cosa: get_wait_data returning after %d retries\n", 999-retries);
+#endif
+ return r;
+ }
+ /* sleep if not ready to read */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+ printk(KERN_INFO "cosa: timeout in get_wait_data (status 0x%x)\n",
+ cosa_getstatus(cosa));
+ return -1;
+}
+
+/*
+ * This routine puts the data byte to the card waiting for the SR_TX_RDY
+ * bit to be set in a loop. It should be used in the exceptional cases
+ * only (for example when resetting the card or downloading the firmware).
+ */
+static int put_wait_data(struct cosa_data *cosa, int data)
+{
+ int retries = 1000;
+ while (--retries) {
+ /* read data and return them */
+ if (cosa_getstatus(cosa) & SR_TX_RDY) {
+ cosa_putdata8(cosa, data);
+#if 0
+ printk(KERN_INFO "Putdata: %d retries\n", 999-retries);
+#endif
+ return 0;
+ }
+#if 0
+ /* sleep if not ready to read */
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1);
+#endif
+ }
+ printk(KERN_INFO "cosa%d: timeout in put_wait_data (status 0x%x)\n",
+ cosa->num, cosa_getstatus(cosa));
+ return -1;
+}
+
+/*
+ * The following routine puts the hexadecimal number into the SRP monitor
+ * and verifies the proper echo of the sent bytes. Returns 0 on success,
+ * negative number on failure (-1,-3,-5,-7) means that put_wait_data() failed,
+ * (-2,-4,-6,-8) means that reading echo failed.
+ */
+static int puthexnumber(struct cosa_data *cosa, int number)
+{
+ char temp[5];
+ int i;
+
+ /* Well, I should probably replace this by something faster. */
+ sprintf(temp, "%04X", number);
+ for (i=0; i<4; i++) {
+ if (put_wait_data(cosa, temp[i]) == -1) {
+ printk(KERN_NOTICE "cosa%d: puthexnumber failed to write byte %d\n",
+ cosa->num, i);
+ return -1-2*i;
+ }
+ if (get_wait_data(cosa) != temp[i]) {
+ printk(KERN_NOTICE "cosa%d: puthexhumber failed to read echo of byte %d\n",
+ cosa->num, i);
+ return -2-2*i;
+ }
+ }
+ return 0;
+}
+
+
+/* ---------- Interrupt routines ---------- */
+
+/*
+ * There are three types of interrupt:
+ * At the beginning of transmit - this handled is in tx_interrupt(),
+ * at the beginning of receive - it is in rx_interrupt() and
+ * at the end of transmit/receive - it is the eot_interrupt() function.
+ * These functions are multiplexed by cosa_interrupt() according to the
+ * COSA status byte. I have moved the rx/tx/eot interrupt handling into
+ * separate functions to make it more readable. These functions are inline,
+ * so there should be no overhead of function call.
+ *
+ * In the COSA bus-master mode, we need to tell the card the address of a
+ * buffer. Unfortunately, COSA may be too slow for us, so we must busy-wait.
+ * It's time to use the bottom half :-(
+ */
+
+/*
+ * Transmit interrupt routine - called when COSA is willing to obtain
+ * data from the OS. The most tricky part of the routine is selection
+ * of channel we (OS) want to send packet for. For SRP we should probably
+ * use the round-robin approach. The newer COSA firmwares have a simple
+ * flow-control - in the status word has bits 2 and 3 set to 1 means that the
+ * channel 0 or 1 doesn't want to receive data.
+ *
+ * It seems there is a bug in COSA firmware (need to trace it further):
+ * When the driver status says that the kernel has no more data for transmit
+ * (e.g. at the end of TX DMA) and then the kernel changes its mind
+ * (e.g. new packet is queued to hard_start_xmit()), the card issues
+ * the TX interrupt but does not mark the channel as ready-to-transmit.
+ * The fix seems to be to push the packet to COSA despite its request.
+ * We first try to obey the card's opinion, and then fall back to forced TX.
+ */
+static inline void tx_interrupt(struct cosa_data *cosa, int status)
+{
+ unsigned long flags, flags1;
+#ifdef DEBUG_IRQS
+ printk(KERN_INFO "cosa%d: SR_DOWN_REQUEST status=0x%04x\n",
+ cosa->num, status);
+#endif
+ spin_lock_irqsave(&cosa->lock, flags);
+ set_bit(TXBIT, &cosa->rxtx);
+ if (!test_bit(IRQBIT, &cosa->rxtx)) {
+ /* flow control, see the comment above */
+ int i=0;
+ if (!cosa->txbitmap) {
+ printk(KERN_WARNING "%s: No channel wants data "
+ "in TX IRQ. Expect DMA timeout.",
+ cosa->name);
+ put_driver_status_nolock(cosa);
+ clear_bit(TXBIT, &cosa->rxtx);
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return;
+ }
+ while(1) {
+ cosa->txchan++;
+ i++;
+ if (cosa->txchan >= cosa->nchannels)
+ cosa->txchan = 0;
+ if (!(cosa->txbitmap & (1<<cosa->txchan)))
+ continue;
+ if (~status & (1 << (cosa->txchan+DRIVER_TXMAP_SHIFT)))
+ break;
+ /* in second pass, accept first ready-to-TX channel */
+ if (i > cosa->nchannels) {
+ /* Can be safely ignored */
+#ifdef DEBUG_IRQS
+ printk(KERN_DEBUG "%s: Forcing TX "
+ "to not-ready channel %d\n",
+ cosa->name, cosa->txchan);
+#endif
+ break;
+ }
+ }
+
+ cosa->txsize = cosa->chan[cosa->txchan].txsize;
+ if (cosa_dma_able(cosa->chan+cosa->txchan,
+ cosa->chan[cosa->txchan].txbuf, cosa->txsize)) {
+ cosa->txbuf = cosa->chan[cosa->txchan].txbuf;
+ } else {
+ memcpy(cosa->bouncebuf, cosa->chan[cosa->txchan].txbuf,
+ cosa->txsize);
+ cosa->txbuf = cosa->bouncebuf;
+ }
+ }
+
+ if (is_8bit(cosa)) {
+ if (!test_bit(IRQBIT, &cosa->rxtx)) {
+ cosa_putstatus(cosa, SR_TX_INT_ENA);
+ cosa_putdata8(cosa, ((cosa->txchan << 5) & 0xe0)|
+ ((cosa->txsize >> 8) & 0x1f));
+#ifdef DEBUG_IO
+ debug_status_out(cosa, SR_TX_INT_ENA);
+ debug_data_out(cosa, ((cosa->txchan << 5) & 0xe0)|
+ ((cosa->txsize >> 8) & 0x1f));
+ debug_data_in(cosa, cosa_getdata8(cosa));
+#else
+ cosa_getdata8(cosa);
+#endif
+ set_bit(IRQBIT, &cosa->rxtx);
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return;
+ } else {
+ clear_bit(IRQBIT, &cosa->rxtx);
+ cosa_putstatus(cosa, 0);
+ cosa_putdata8(cosa, cosa->txsize&0xff);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, 0);
+ debug_data_out(cosa, cosa->txsize&0xff);
+#endif
+ }
+ } else {
+ cosa_putstatus(cosa, SR_TX_INT_ENA);
+ cosa_putdata16(cosa, ((cosa->txchan<<13) & 0xe000)
+ | (cosa->txsize & 0x1fff));
+#ifdef DEBUG_IO
+ debug_status_out(cosa, SR_TX_INT_ENA);
+ debug_data_out(cosa, ((cosa->txchan<<13) & 0xe000)
+ | (cosa->txsize & 0x1fff));
+ debug_data_in(cosa, cosa_getdata8(cosa));
+ debug_status_out(cosa, 0);
+#else
+ cosa_getdata8(cosa);
+#endif
+ cosa_putstatus(cosa, 0);
+ }
+
+ if (cosa->busmaster) {
+ unsigned long addr = virt_to_bus(cosa->txbuf);
+ int count=0;
+ printk(KERN_INFO "busmaster IRQ\n");
+ while (!(cosa_getstatus(cosa)&SR_TX_RDY)) {
+ count++;
+ udelay(10);
+ if (count > 1000) break;
+ }
+ printk(KERN_INFO "status %x\n", cosa_getstatus(cosa));
+ printk(KERN_INFO "ready after %d loops\n", count);
+ cosa_putdata16(cosa, (addr >> 16)&0xffff);
+
+ count = 0;
+ while (!(cosa_getstatus(cosa)&SR_TX_RDY)) {
+ count++;
+ if (count > 1000) break;
+ udelay(10);
+ }
+ printk(KERN_INFO "ready after %d loops\n", count);
+ cosa_putdata16(cosa, addr &0xffff);
+ flags1 = claim_dma_lock();
+ set_dma_mode(cosa->dma, DMA_MODE_CASCADE);
+ enable_dma(cosa->dma);
+ release_dma_lock(flags1);
+ } else {
+ /* start the DMA */
+ flags1 = claim_dma_lock();
+ disable_dma(cosa->dma);
+ clear_dma_ff(cosa->dma);
+ set_dma_mode(cosa->dma, DMA_MODE_WRITE);
+ set_dma_addr(cosa->dma, virt_to_bus(cosa->txbuf));
+ set_dma_count(cosa->dma, cosa->txsize);
+ enable_dma(cosa->dma);
+ release_dma_lock(flags1);
+ }
+ cosa_putstatus(cosa, SR_TX_DMA_ENA|SR_USR_INT_ENA);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, SR_TX_DMA_ENA|SR_USR_INT_ENA);
+#endif
+ spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static inline void rx_interrupt(struct cosa_data *cosa, int status)
+{
+ unsigned long flags;
+#ifdef DEBUG_IRQS
+ printk(KERN_INFO "cosa%d: SR_UP_REQUEST\n", cosa->num);
+#endif
+
+ spin_lock_irqsave(&cosa->lock, flags);
+ set_bit(RXBIT, &cosa->rxtx);
+
+ if (is_8bit(cosa)) {
+ if (!test_bit(IRQBIT, &cosa->rxtx)) {
+ set_bit(IRQBIT, &cosa->rxtx);
+ put_driver_status_nolock(cosa);
+ cosa->rxsize = cosa_getdata8(cosa) <<8;
+#ifdef DEBUG_IO
+ debug_data_in(cosa, cosa->rxsize >> 8);
+#endif
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ return;
+ } else {
+ clear_bit(IRQBIT, &cosa->rxtx);
+ cosa->rxsize |= cosa_getdata8(cosa) & 0xff;
+#ifdef DEBUG_IO
+ debug_data_in(cosa, cosa->rxsize & 0xff);
+#endif
+#if 0
+ printk(KERN_INFO "cosa%d: receive rxsize = (0x%04x).\n",
+ cosa->num, cosa->rxsize);
+#endif
+ }
+ } else {
+ cosa->rxsize = cosa_getdata16(cosa);
+#ifdef DEBUG_IO
+ debug_data_in(cosa, cosa->rxsize);
+#endif
+#if 0
+ printk(KERN_INFO "cosa%d: receive rxsize = (0x%04x).\n",
+ cosa->num, cosa->rxsize);
+#endif
+ }
+ if (((cosa->rxsize & 0xe000) >> 13) >= cosa->nchannels) {
+ printk(KERN_WARNING "%s: rx for unknown channel (0x%04x)\n",
+ cosa->name, cosa->rxsize);
+ spin_unlock_irqrestore(&cosa->lock, flags);
+ goto reject;
+ }
+ cosa->rxchan = cosa->chan + ((cosa->rxsize & 0xe000) >> 13);
+ cosa->rxsize &= 0x1fff;
+ spin_unlock_irqrestore(&cosa->lock, flags);
+
+ cosa->rxbuf = NULL;
+ if (cosa->rxchan->setup_rx)
+ cosa->rxbuf = cosa->rxchan->setup_rx(cosa->rxchan, cosa->rxsize);
+
+ if (!cosa->rxbuf) {
+reject: /* Reject the packet */
+ printk(KERN_INFO "cosa%d: rejecting packet on channel %d\n",
+ cosa->num, cosa->rxchan->num);
+ cosa->rxbuf = cosa->bouncebuf;
+ }
+
+ /* start the DMA */
+ flags = claim_dma_lock();
+ disable_dma(cosa->dma);
+ clear_dma_ff(cosa->dma);
+ set_dma_mode(cosa->dma, DMA_MODE_READ);
+ if (cosa_dma_able(cosa->rxchan, cosa->rxbuf, cosa->rxsize & 0x1fff)) {
+ set_dma_addr(cosa->dma, virt_to_bus(cosa->rxbuf));
+ } else {
+ set_dma_addr(cosa->dma, virt_to_bus(cosa->bouncebuf));
+ }
+ set_dma_count(cosa->dma, (cosa->rxsize&0x1fff));
+ enable_dma(cosa->dma);
+ release_dma_lock(flags);
+ spin_lock_irqsave(&cosa->lock, flags);
+ cosa_putstatus(cosa, SR_RX_DMA_ENA|SR_USR_INT_ENA);
+ if (!is_8bit(cosa) && (status & SR_TX_RDY))
+ cosa_putdata8(cosa, DRIVER_RX_READY);
+#ifdef DEBUG_IO
+ debug_status_out(cosa, SR_RX_DMA_ENA|SR_USR_INT_ENA);
+ if (!is_8bit(cosa) && (status & SR_TX_RDY))
+ debug_data_cmd(cosa, DRIVER_RX_READY);
+#endif
+ spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static inline void eot_interrupt(struct cosa_data *cosa, int status)
+{
+ unsigned long flags, flags1;
+ spin_lock_irqsave(&cosa->lock, flags);
+ flags1 = claim_dma_lock();
+ disable_dma(cosa->dma);
+ clear_dma_ff(cosa->dma);
+ release_dma_lock(flags1);
+ if (test_bit(TXBIT, &cosa->rxtx)) {
+ struct channel_data *chan = cosa->chan+cosa->txchan;
+ if (chan->tx_done)
+ if (chan->tx_done(chan, cosa->txsize))
+ clear_bit(chan->num, &cosa->txbitmap);
+ } else if (test_bit(RXBIT, &cosa->rxtx)) {
+#ifdef DEBUG_DATA
+ {
+ int i;
+ printk(KERN_INFO "cosa%dc%d: done rx(0x%x)", cosa->num,
+ cosa->rxchan->num, cosa->rxsize);
+ for (i=0; i<cosa->rxsize; i++)
+ printk (" %02x", cosa->rxbuf[i]&0xff);
+ printk("\n");
+ }
+#endif
+ /* Packet for unknown channel? */
+ if (cosa->rxbuf == cosa->bouncebuf)
+ goto out;
+ if (!cosa_dma_able(cosa->rxchan, cosa->rxbuf, cosa->rxsize))
+ memcpy(cosa->rxbuf, cosa->bouncebuf, cosa->rxsize);
+ if (cosa->rxchan->rx_done)
+ if (cosa->rxchan->rx_done(cosa->rxchan))
+ clear_bit(cosa->rxchan->num, &cosa->rxbitmap);
+ } else {
+ printk(KERN_NOTICE "cosa%d: unexpected EOT interrupt\n",
+ cosa->num);
+ }
+ /*
+ * Clear the RXBIT, TXBIT and IRQBIT (the latest should be
+ * cleared anyway). We should do it as soon as possible
+ * so that we can tell the COSA we are done and to give it a time
+ * for recovery.
+ */
+out:
+ cosa->rxtx = 0;
+ put_driver_status_nolock(cosa);
+ spin_unlock_irqrestore(&cosa->lock, flags);
+}
+
+static irqreturn_t cosa_interrupt(int irq, void *cosa_, struct pt_regs *regs)
+{
+ unsigned status;
+ int count = 0;
+ struct cosa_data *cosa = cosa_;
+again:
+ status = cosa_getstatus(cosa);
+#ifdef DEBUG_IRQS
+ printk(KERN_INFO "cosa%d: got IRQ, status 0x%02x\n", cosa->num,
+ status & 0xff);
+#endif
+#ifdef DEBUG_IO
+ debug_status_in(cosa, status);
+#endif
+ switch (status & SR_CMD_FROM_SRP_MASK) {
+ case SR_DOWN_REQUEST:
+ tx_interrupt(cosa, status);
+ break;
+ case SR_UP_REQUEST:
+ rx_interrupt(cosa, status);
+ break;
+ case SR_END_OF_TRANSFER:
+ eot_interrupt(cosa, status);
+ break;
+ default:
+ /* We may be too fast for SRP. Try to wait a bit more. */
+ if (count++ < 100) {
+ udelay(100);
+ goto again;
+ }
+ printk(KERN_INFO "cosa%d: unknown status 0x%02x in IRQ after %d retries\n",
+ cosa->num, status & 0xff, count);
+ }
+#ifdef DEBUG_IRQS
+ if (count)
+ printk(KERN_INFO "%s: %d-times got unknown status in IRQ\n",
+ cosa->name, count);
+ else
+ printk(KERN_INFO "%s: returning from IRQ\n", cosa->name);
+#endif
+ return IRQ_HANDLED;
+}
+
+
+/* ---------- I/O debugging routines ---------- */
+/*
+ * These routines can be used to monitor COSA/SRP I/O and to printk()
+ * the data being transferred on the data and status I/O port in a
+ * readable way.
+ */
+
+#ifdef DEBUG_IO
+static void debug_status_in(struct cosa_data *cosa, int status)
+{
+ char *s;
+ switch(status & SR_CMD_FROM_SRP_MASK) {
+ case SR_UP_REQUEST:
+ s = "RX_REQ";
+ break;
+ case SR_DOWN_REQUEST:
+ s = "TX_REQ";
+ break;
+ case SR_END_OF_TRANSFER:
+ s = "ET_REQ";
+ break;
+ default:
+ s = "NO_REQ";
+ break;
+ }
+ printk(KERN_INFO "%s: IO: status -> 0x%02x (%s%s%s%s)\n",
+ cosa->name,
+ status,
+ status & SR_USR_RQ ? "USR_RQ|":"",
+ status & SR_TX_RDY ? "TX_RDY|":"",
+ status & SR_RX_RDY ? "RX_RDY|":"",
+ s);
+}
+
+static void debug_status_out(struct cosa_data *cosa, int status)
+{
+ printk(KERN_INFO "%s: IO: status <- 0x%02x (%s%s%s%s%s%s)\n",
+ cosa->name,
+ status,
+ status & SR_RX_DMA_ENA ? "RXDMA|":"!rxdma|",
+ status & SR_TX_DMA_ENA ? "TXDMA|":"!txdma|",
+ status & SR_RST ? "RESET|":"",
+ status & SR_USR_INT_ENA ? "USRINT|":"!usrint|",
+ status & SR_TX_INT_ENA ? "TXINT|":"!txint|",
+ status & SR_RX_INT_ENA ? "RXINT":"!rxint");
+}
+
+static void debug_data_in(struct cosa_data *cosa, int data)
+{
+ printk(KERN_INFO "%s: IO: data -> 0x%04x\n", cosa->name, data);
+}
+
+static void debug_data_out(struct cosa_data *cosa, int data)
+{
+ printk(KERN_INFO "%s: IO: data <- 0x%04x\n", cosa->name, data);
+}
+
+static void debug_data_cmd(struct cosa_data *cosa, int data)
+{
+ printk(KERN_INFO "%s: IO: data <- 0x%04x (%s|%s)\n",
+ cosa->name, data,
+ data & SR_RDY_RCV ? "RX_RDY" : "!rx_rdy",
+ data & SR_RDY_SND ? "TX_RDY" : "!tx_rdy");
+}
+#endif
+
+/* EOF -- this file has not been truncated */
diff --git a/drivers/net/wan/cosa.h b/drivers/net/wan/cosa.h
new file mode 100644
index 00000000000..028f3d96b97
--- /dev/null
+++ b/drivers/net/wan/cosa.h
@@ -0,0 +1,117 @@
+/* $Id: cosa.h,v 1.6 1999/01/06 14:02:44 kas Exp $ */
+
+/*
+ * Copyright (C) 1995-1997 Jan "Yenya" Kasprzak <kas@fi.muni.cz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef COSA_H__
+#define COSA_H__
+
+#include <linux/ioctl.h>
+
+#ifdef __KERNEL__
+/* status register - output bits */
+#define SR_RX_DMA_ENA 0x04 /* receiver DMA enable bit */
+#define SR_TX_DMA_ENA 0x08 /* transmitter DMA enable bit */
+#define SR_RST 0x10 /* SRP reset */
+#define SR_USR_INT_ENA 0x20 /* user interrupt enable bit */
+#define SR_TX_INT_ENA 0x40 /* transmitter interrupt enable bit */
+#define SR_RX_INT_ENA 0x80 /* receiver interrupt enable bit */
+
+/* status register - input bits */
+#define SR_USR_RQ 0x20 /* user interrupt request pending */
+#define SR_TX_RDY 0x40 /* transmitter empty (ready) */
+#define SR_RX_RDY 0x80 /* receiver data ready */
+
+#define SR_UP_REQUEST 0x02 /* request from SRP to transfer data
+ up to PC */
+#define SR_DOWN_REQUEST 0x01 /* SRP is able to transfer data down
+ from PC to SRP */
+#define SR_END_OF_TRANSFER 0x03 /* SRP signalize end of
+ transfer (up or down) */
+
+#define SR_CMD_FROM_SRP_MASK 0x03 /* mask to get SRP command */
+
+/* bits in driver status byte definitions : */
+#define SR_RDY_RCV 0x01 /* ready to receive packet */
+#define SR_RDY_SND 0x02 /* ready to send packet */
+#define SR_CMD_PND 0x04 /* command pending */ /* not currently used */
+
+/* ???? */
+#define SR_PKT_UP 0x01 /* transfer of packet up in progress */
+#define SR_PKT_DOWN 0x02 /* transfer of packet down in progress */
+
+#endif /* __KERNEL__ */
+
+#define SR_LOAD_ADDR 0x4400 /* SRP microcode load address */
+#define SR_START_ADDR 0x4400 /* SRP microcode start address */
+
+#define COSA_LOAD_ADDR 0x400 /* SRP microcode load address */
+#define COSA_MAX_FIRMWARE_SIZE 0x10000
+
+/* ioctls */
+struct cosa_download {
+ int addr, len;
+ char __user *code;
+};
+
+/* Reset the device */
+#define COSAIORSET _IO('C',0xf0)
+
+/* Start microcode at given address */
+#define COSAIOSTRT _IOW('C',0xf1, int)
+
+/* Read the block from the device memory */
+#define COSAIORMEM _IOWR('C',0xf2, struct cosa_download *)
+ /* actually the struct cosa_download itself; this is to keep
+ * the ioctl number same as in 2.4 in order to keep the user-space
+ * utils compatible. */
+
+/* Write the block to the device memory (i.e. download the microcode) */
+#define COSAIODOWNLD _IOW('C',0xf2, struct cosa_download *)
+ /* actually the struct cosa_download itself; this is to keep
+ * the ioctl number same as in 2.4 in order to keep the user-space
+ * utils compatible. */
+
+/* Read the device type (one of "srp", "cosa", and "cosa8" for now) */
+#define COSAIORTYPE _IOR('C',0xf3, char *)
+
+/* Read the device identification string */
+#define COSAIORIDSTR _IOR('C',0xf4, char *)
+/* Maximum length of the identification string. */
+#define COSA_MAX_ID_STRING 128
+
+/* Increment/decrement the module usage count :-) */
+/* #define COSAIOMINC _IO('C',0xf5) */
+/* #define COSAIOMDEC _IO('C',0xf6) */
+
+/* Get the total number of cards installed */
+#define COSAIONRCARDS _IO('C',0xf7)
+
+/* Get the number of channels on this card */
+#define COSAIONRCHANS _IO('C',0xf8)
+
+/* Set the driver for the bus-master operations */
+#define COSAIOBMSET _IOW('C', 0xf9, unsigned short)
+
+#define COSA_BM_OFF 0 /* Bus-mastering off - use ISA DMA (default) */
+#define COSA_BM_ON 1 /* Bus-mastering on - faster but untested */
+
+/* Gets the busmaster status */
+#define COSAIOBMGET _IO('C', 0xfa)
+
+#endif /* !COSA_H__ */
diff --git a/drivers/net/wan/cycx_drv.c b/drivers/net/wan/cycx_drv.c
new file mode 100644
index 00000000000..6e74af62ca0
--- /dev/null
+++ b/drivers/net/wan/cycx_drv.c
@@ -0,0 +1,586 @@
+/*
+* cycx_drv.c Cyclom 2X Support Module.
+*
+* This module is a library of common hardware specific
+* functions used by the Cyclades Cyclom 2X sync card.
+*
+* Author: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+*
+* Copyright: (c) 1998-2003 Arnaldo Carvalho de Melo
+*
+* Based on sdladrv.c by Gene Kozin <genek@compuserve.com>
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* 1999/11/11 acme set_current_state(TASK_INTERRUPTIBLE), code
+* cleanup
+* 1999/11/08 acme init_cyc2x deleted, doing nothing
+* 1999/11/06 acme back to read[bw], write[bw] and memcpy_to and
+* fromio to use dpmbase ioremaped
+* 1999/10/26 acme use isa_read[bw], isa_write[bw] & isa_memcpy_to
+* & fromio
+* 1999/10/23 acme cleanup to only supports cyclom2x: all the other
+* boards are no longer manufactured by cyclades,
+* if someone wants to support them... be my guest!
+* 1999/05/28 acme cycx_intack & cycx_intde gone for good
+* 1999/05/18 acme lots of unlogged work, submitting to Linus...
+* 1999/01/03 acme more judicious use of data types
+* 1999/01/03 acme judicious use of data types :>
+* cycx_inten trying to reset pending interrupts
+* from cyclom 2x - I think this isn't the way to
+* go, but for now...
+* 1999/01/02 acme cycx_intack ok, I think there's nothing to do
+* to ack an int in cycx_drv.c, only handle it in
+* cyx_isr (or in the other protocols: cyp_isr,
+* cyf_isr, when they get implemented.
+* Dec 31, 1998 acme cycx_data_boot & cycx_code_boot fixed, crossing
+* fingers to see x25_configure in cycx_x25.c
+* work... :)
+* Dec 26, 1998 acme load implementation fixed, seems to work! :)
+* cycx_2x_dpmbase_options with all the possible
+* DPM addresses (20).
+* cycx_intr implemented (test this!)
+* general code cleanup
+* Dec 8, 1998 Ivan Passos Cyclom-2X firmware load implementation.
+* Aug 8, 1998 acme Initial version.
+*/
+
+#include <linux/init.h> /* __init */
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/sched.h> /* for jiffies, HZ, etc. */
+#include <linux/cycx_drv.h> /* API definitions */
+#include <linux/cycx_cfm.h> /* CYCX firmware module definitions */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* read[wl], write[wl], ioremap, iounmap */
+
+#define MOD_VERSION 0
+#define MOD_RELEASE 6
+
+MODULE_AUTHOR("Arnaldo Carvalho de Melo");
+MODULE_DESCRIPTION("Cyclom 2x Sync Card Driver");
+MODULE_LICENSE("GPL");
+
+/* Hardware-specific functions */
+static int load_cyc2x(struct cycx_hw *hw, struct cycx_firmware *cfm, u32 len);
+static void cycx_bootcfg(struct cycx_hw *hw);
+
+static int reset_cyc2x(void __iomem *addr);
+static int detect_cyc2x(void __iomem *addr);
+
+/* Miscellaneous functions */
+static void delay_cycx(int sec);
+static int get_option_index(long *optlist, long optval);
+static u16 checksum(u8 *buf, u32 len);
+
+#define wait_cyc(addr) cycx_exec(addr + CMD_OFFSET)
+
+/* Global Data */
+
+/* private data */
+static char modname[] = "cycx_drv";
+static char fullname[] = "Cyclom 2X Support Module";
+static char copyright[] = "(c) 1998-2003 Arnaldo Carvalho de Melo "
+ "<acme@conectiva.com.br>";
+
+/* Hardware configuration options.
+ * These are arrays of configuration options used by verification routines.
+ * The first element of each array is its size (i.e. number of options).
+ */
+static long cyc2x_dpmbase_options[] = {
+ 20,
+ 0xA0000, 0xA4000, 0xA8000, 0xAC000, 0xB0000, 0xB4000, 0xB8000,
+ 0xBC000, 0xC0000, 0xC4000, 0xC8000, 0xCC000, 0xD0000, 0xD4000,
+ 0xD8000, 0xDC000, 0xE0000, 0xE4000, 0xE8000, 0xEC000
+};
+
+static long cycx_2x_irq_options[] = { 7, 3, 5, 9, 10, 11, 12, 15 };
+
+/* Kernel Loadable Module Entry Points */
+/* Module 'insert' entry point.
+ * o print announcement
+ * o initialize static data
+ *
+ * Return: 0 Ok
+ * < 0 error.
+ * Context: process */
+
+int __init cycx_drv_init(void)
+{
+ printk(KERN_INFO "%s v%u.%u %s\n", fullname, MOD_VERSION, MOD_RELEASE,
+ copyright);
+
+ return 0;
+}
+
+/* Module 'remove' entry point.
+ * o release all remaining system resources */
+void cycx_drv_cleanup(void)
+{
+}
+
+/* Kernel APIs */
+/* Set up adapter.
+ * o detect adapter type
+ * o verify hardware configuration options
+ * o check for hardware conflicts
+ * o set up adapter shared memory
+ * o test adapter memory
+ * o load firmware
+ * Return: 0 ok.
+ * < 0 error */
+EXPORT_SYMBOL(cycx_setup);
+int cycx_setup(struct cycx_hw *hw, void *cfm, u32 len, unsigned long dpmbase)
+{
+ int err;
+
+ /* Verify IRQ configuration options */
+ if (!get_option_index(cycx_2x_irq_options, hw->irq)) {
+ printk(KERN_ERR "%s: IRQ %d is invalid!\n", modname, hw->irq);
+ return -EINVAL;
+ }
+
+ /* Setup adapter dual-port memory window and test memory */
+ if (!dpmbase) {
+ printk(KERN_ERR "%s: you must specify the dpm address!\n",
+ modname);
+ return -EINVAL;
+ } else if (!get_option_index(cyc2x_dpmbase_options, dpmbase)) {
+ printk(KERN_ERR "%s: memory address 0x%lX is invalid!\n",
+ modname, dpmbase);
+ return -EINVAL;
+ }
+
+ hw->dpmbase = ioremap(dpmbase, CYCX_WINDOWSIZE);
+ hw->dpmsize = CYCX_WINDOWSIZE;
+
+ if (!detect_cyc2x(hw->dpmbase)) {
+ printk(KERN_ERR "%s: adapter Cyclom 2X not found at "
+ "address 0x%lX!\n", modname, dpmbase);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "%s: found Cyclom 2X card at address 0x%lX.\n",
+ modname, dpmbase);
+
+ /* Load firmware. If loader fails then shut down adapter */
+ err = load_cyc2x(hw, cfm, len);
+
+ if (err)
+ cycx_down(hw); /* shutdown adapter */
+
+ return err;
+}
+
+EXPORT_SYMBOL(cycx_down);
+int cycx_down(struct cycx_hw *hw)
+{
+ iounmap(hw->dpmbase);
+ return 0;
+}
+
+/* Enable interrupt generation. */
+EXPORT_SYMBOL(cycx_inten);
+void cycx_inten(struct cycx_hw *hw)
+{
+ writeb(0, hw->dpmbase);
+}
+
+/* Generate an interrupt to adapter's CPU. */
+EXPORT_SYMBOL(cycx_intr);
+void cycx_intr(struct cycx_hw *hw)
+{
+ writew(0, hw->dpmbase + GEN_CYCX_INTR);
+}
+
+/* Execute Adapter Command.
+ * o Set exec flag.
+ * o Busy-wait until flag is reset. */
+EXPORT_SYMBOL(cycx_exec);
+int cycx_exec(void __iomem *addr)
+{
+ u16 i = 0;
+ /* wait till addr content is zeroed */
+
+ while (readw(addr)) {
+ udelay(1000);
+
+ if (++i > 50)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Read absolute adapter memory.
+ * Transfer data from adapter's memory to data buffer. */
+EXPORT_SYMBOL(cycx_peek);
+int cycx_peek(struct cycx_hw *hw, u32 addr, void *buf, u32 len)
+{
+ if (len == 1)
+ *(u8*)buf = readb(hw->dpmbase + addr);
+ else
+ memcpy_fromio(buf, hw->dpmbase + addr, len);
+
+ return 0;
+}
+
+/* Write Absolute Adapter Memory.
+ * Transfer data from data buffer to adapter's memory. */
+EXPORT_SYMBOL(cycx_poke);
+int cycx_poke(struct cycx_hw *hw, u32 addr, void *buf, u32 len)
+{
+ if (len == 1)
+ writeb(*(u8*)buf, hw->dpmbase + addr);
+ else
+ memcpy_toio(hw->dpmbase + addr, buf, len);
+
+ return 0;
+}
+
+/* Hardware-Specific Functions */
+
+/* Load Aux Routines */
+/* Reset board hardware.
+ return 1 if memory exists at addr and 0 if not. */
+static int memory_exists(void __iomem *addr)
+{
+ int tries = 0;
+
+ for (; tries < 3 ; tries++) {
+ writew(TEST_PATTERN, addr + 0x10);
+
+ if (readw(addr + 0x10) == TEST_PATTERN)
+ if (readw(addr + 0x10) == TEST_PATTERN)
+ return 1;
+
+ delay_cycx(1);
+ }
+
+ return 0;
+}
+
+/* Load reset code. */
+static void reset_load(void __iomem *addr, u8 *buffer, u32 cnt)
+{
+ void __iomem *pt_code = addr + RESET_OFFSET;
+ u16 i; /*, j; */
+
+ for (i = 0 ; i < cnt ; i++) {
+/* for (j = 0 ; j < 50 ; j++); Delay - FIXME busy waiting... */
+ writeb(*buffer++, pt_code++);
+ }
+}
+
+/* Load buffer using boot interface.
+ * o copy data from buffer to Cyclom-X memory
+ * o wait for reset code to copy it to right portion of memory */
+static int buffer_load(void __iomem *addr, u8 *buffer, u32 cnt)
+{
+ memcpy_toio(addr + DATA_OFFSET, buffer, cnt);
+ writew(GEN_BOOT_DAT, addr + CMD_OFFSET);
+
+ return wait_cyc(addr);
+}
+
+/* Set up entry point and kick start Cyclom-X CPU. */
+static void cycx_start(void __iomem *addr)
+{
+ /* put in 0x30 offset the jump instruction to the code entry point */
+ writeb(0xea, addr + 0x30);
+ writeb(0x00, addr + 0x31);
+ writeb(0xc4, addr + 0x32);
+ writeb(0x00, addr + 0x33);
+ writeb(0x00, addr + 0x34);
+
+ /* cmd to start executing code */
+ writew(GEN_START, addr + CMD_OFFSET);
+}
+
+/* Load and boot reset code. */
+static void cycx_reset_boot(void __iomem *addr, u8 *code, u32 len)
+{
+ void __iomem *pt_start = addr + START_OFFSET;
+
+ writeb(0xea, pt_start++); /* jmp to f000:3f00 */
+ writeb(0x00, pt_start++);
+ writeb(0xfc, pt_start++);
+ writeb(0x00, pt_start++);
+ writeb(0xf0, pt_start);
+ reset_load(addr, code, len);
+
+ /* 80186 was in hold, go */
+ writeb(0, addr + START_CPU);
+ delay_cycx(1);
+}
+
+/* Load data.bin file through boot (reset) interface. */
+static int cycx_data_boot(void __iomem *addr, u8 *code, u32 len)
+{
+ void __iomem *pt_boot_cmd = addr + CMD_OFFSET;
+ u32 i;
+
+ /* boot buffer lenght */
+ writew(CFM_LOAD_BUFSZ, pt_boot_cmd + sizeof(u16));
+ writew(GEN_DEFPAR, pt_boot_cmd);
+
+ if (wait_cyc(addr) < 0)
+ return -1;
+
+ writew(0, pt_boot_cmd + sizeof(u16));
+ writew(0x4000, pt_boot_cmd + 2 * sizeof(u16));
+ writew(GEN_SET_SEG, pt_boot_cmd);
+
+ if (wait_cyc(addr) < 0)
+ return -1;
+
+ for (i = 0 ; i < len ; i += CFM_LOAD_BUFSZ)
+ if (buffer_load(addr, code + i,
+ min_t(u32, CFM_LOAD_BUFSZ, (len - i))) < 0) {
+ printk(KERN_ERR "%s: Error !!\n", modname);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Load code.bin file through boot (reset) interface. */
+static int cycx_code_boot(void __iomem *addr, u8 *code, u32 len)
+{
+ void __iomem *pt_boot_cmd = addr + CMD_OFFSET;
+ u32 i;
+
+ /* boot buffer lenght */
+ writew(CFM_LOAD_BUFSZ, pt_boot_cmd + sizeof(u16));
+ writew(GEN_DEFPAR, pt_boot_cmd);
+
+ if (wait_cyc(addr) < 0)
+ return -1;
+
+ writew(0x0000, pt_boot_cmd + sizeof(u16));
+ writew(0xc400, pt_boot_cmd + 2 * sizeof(u16));
+ writew(GEN_SET_SEG, pt_boot_cmd);
+
+ if (wait_cyc(addr) < 0)
+ return -1;
+
+ for (i = 0 ; i < len ; i += CFM_LOAD_BUFSZ)
+ if (buffer_load(addr, code + i,
+ min_t(u32, CFM_LOAD_BUFSZ, (len - i)))) {
+ printk(KERN_ERR "%s: Error !!\n", modname);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Load adapter from the memory image of the CYCX firmware module.
+ * o verify firmware integrity and compatibility
+ * o start adapter up */
+static int load_cyc2x(struct cycx_hw *hw, struct cycx_firmware *cfm, u32 len)
+{
+ int i, j;
+ struct cycx_fw_header *img_hdr;
+ u8 *reset_image,
+ *data_image,
+ *code_image;
+ void __iomem *pt_cycld = hw->dpmbase + 0x400;
+ u16 cksum;
+
+ /* Announce */
+ printk(KERN_INFO "%s: firmware signature=\"%s\"\n", modname,
+ cfm->signature);
+
+ /* Verify firmware signature */
+ if (strcmp(cfm->signature, CFM_SIGNATURE)) {
+ printk(KERN_ERR "%s:load_cyc2x: not Cyclom-2X firmware!\n",
+ modname);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "%s: firmware version=%u\n", modname, cfm->version);
+
+ /* Verify firmware module format version */
+ if (cfm->version != CFM_VERSION) {
+ printk(KERN_ERR "%s:%s: firmware format %u rejected! "
+ "Expecting %u.\n",
+ modname, __FUNCTION__, cfm->version, CFM_VERSION);
+ return -EINVAL;
+ }
+
+ /* Verify firmware module length and checksum */
+ cksum = checksum((u8*)&cfm->info, sizeof(struct cycx_fw_info) +
+ cfm->info.codesize);
+/*
+ FIXME cfm->info.codesize is off by 2
+ if (((len - sizeof(struct cycx_firmware) - 1) != cfm->info.codesize) ||
+*/
+ if (cksum != cfm->checksum) {
+ printk(KERN_ERR "%s:%s: firmware corrupted!\n",
+ modname, __FUNCTION__);
+ printk(KERN_ERR " cdsize = 0x%x (expected 0x%lx)\n",
+ len - (int)sizeof(struct cycx_firmware) - 1,
+ cfm->info.codesize);
+ printk(KERN_ERR " chksum = 0x%x (expected 0x%x)\n",
+ cksum, cfm->checksum);
+ return -EINVAL;
+ }
+
+ /* If everything is ok, set reset, data and code pointers */
+ img_hdr = (struct cycx_fw_header *)&cfm->image;
+#ifdef FIRMWARE_DEBUG
+ printk(KERN_INFO "%s:%s: image sizes\n", __FUNCTION__, modname);
+ printk(KERN_INFO " reset=%lu\n", img_hdr->reset_size);
+ printk(KERN_INFO " data=%lu\n", img_hdr->data_size);
+ printk(KERN_INFO " code=%lu\n", img_hdr->code_size);
+#endif
+ reset_image = ((u8 *)img_hdr) + sizeof(struct cycx_fw_header);
+ data_image = reset_image + img_hdr->reset_size;
+ code_image = data_image + img_hdr->data_size;
+
+ /*---- Start load ----*/
+ /* Announce */
+ printk(KERN_INFO "%s: loading firmware %s (ID=%u)...\n", modname,
+ cfm->descr[0] ? cfm->descr : "unknown firmware",
+ cfm->info.codeid);
+
+ for (i = 0 ; i < 5 ; i++) {
+ /* Reset Cyclom hardware */
+ if (!reset_cyc2x(hw->dpmbase)) {
+ printk(KERN_ERR "%s: dpm problem or board not found\n",
+ modname);
+ return -EINVAL;
+ }
+
+ /* Load reset.bin */
+ cycx_reset_boot(hw->dpmbase, reset_image, img_hdr->reset_size);
+ /* reset is waiting for boot */
+ writew(GEN_POWER_ON, pt_cycld);
+ delay_cycx(1);
+
+ for (j = 0 ; j < 3 ; j++)
+ if (!readw(pt_cycld))
+ goto reset_loaded;
+ else
+ delay_cycx(1);
+ }
+
+ printk(KERN_ERR "%s: reset not started.\n", modname);
+ return -EINVAL;
+
+reset_loaded:
+ /* Load data.bin */
+ if (cycx_data_boot(hw->dpmbase, data_image, img_hdr->data_size)) {
+ printk(KERN_ERR "%s: cannot load data file.\n", modname);
+ return -EINVAL;
+ }
+
+ /* Load code.bin */
+ if (cycx_code_boot(hw->dpmbase, code_image, img_hdr->code_size)) {
+ printk(KERN_ERR "%s: cannot load code file.\n", modname);
+ return -EINVAL;
+ }
+
+ /* Prepare boot-time configuration data */
+ cycx_bootcfg(hw);
+
+ /* kick-off CPU */
+ cycx_start(hw->dpmbase);
+
+ /* Arthur Ganzert's tip: wait a while after the firmware loading...
+ seg abr 26 17:17:12 EST 1999 - acme */
+ delay_cycx(7);
+ printk(KERN_INFO "%s: firmware loaded!\n", modname);
+
+ /* enable interrupts */
+ cycx_inten(hw);
+
+ return 0;
+}
+
+/* Prepare boot-time firmware configuration data.
+ * o initialize configuration data area
+ From async.doc - V_3.4.0 - 07/18/1994
+ - As of now, only static buffers are available to the user.
+ So, the bit VD_RXDIRC must be set in 'valid'. That means that user
+ wants to use the static transmission and reception buffers. */
+static void cycx_bootcfg(struct cycx_hw *hw)
+{
+ /* use fixed buffers */
+ writeb(FIXED_BUFFERS, hw->dpmbase + CONF_OFFSET);
+}
+
+/* Detect Cyclom 2x adapter.
+ * Following tests are used to detect Cyclom 2x adapter:
+ * to be completed based on the tests done below
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test. */
+static int detect_cyc2x(void __iomem *addr)
+{
+ reset_cyc2x(addr);
+
+ return memory_exists(addr);
+}
+
+/* Miscellaneous */
+/* Get option's index into the options list.
+ * Return option's index (1 .. N) or zero if option is invalid. */
+static int get_option_index(long *optlist, long optval)
+{
+ int i = 1;
+
+ for (; i <= optlist[0]; ++i)
+ if (optlist[i] == optval)
+ return i;
+
+ return 0;
+}
+
+/* Reset adapter's CPU. */
+static int reset_cyc2x(void __iomem *addr)
+{
+ writeb(0, addr + RST_ENABLE);
+ delay_cycx(2);
+ writeb(0, addr + RST_DISABLE);
+ delay_cycx(2);
+
+ return memory_exists(addr);
+}
+
+/* Delay */
+static void delay_cycx(int sec)
+{
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(sec * HZ);
+}
+
+/* Calculate 16-bit CRC using CCITT polynomial. */
+static u16 checksum(u8 *buf, u32 len)
+{
+ u16 crc = 0;
+ u16 mask, flag;
+
+ for (; len; --len, ++buf)
+ for (mask = 0x80; mask; mask >>= 1) {
+ flag = (crc & 0x8000);
+ crc <<= 1;
+ crc |= ((*buf & mask) ? 1 : 0);
+
+ if (flag)
+ crc ^= 0x1021;
+ }
+
+ return crc;
+}
+
+module_init(cycx_drv_init);
+module_exit(cycx_drv_cleanup);
+
+/* End */
diff --git a/drivers/net/wan/cycx_main.c b/drivers/net/wan/cycx_main.c
new file mode 100644
index 00000000000..7b48064364d
--- /dev/null
+++ b/drivers/net/wan/cycx_main.c
@@ -0,0 +1,351 @@
+/*
+* cycx_main.c Cyclades Cyclom 2X WAN Link Driver. Main module.
+*
+* Author: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+*
+* Copyright: (c) 1998-2003 Arnaldo Carvalho de Melo
+*
+* Based on sdlamain.c by Gene Kozin <genek@compuserve.com> &
+* Jaspreet Singh <jaspreet@sangoma.com>
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Please look at the bitkeeper changelog (or any other scm tool that ends up
+* importing bitkeeper changelog or that replaces bitkeeper in the future as
+* main tool for linux development).
+*
+* 2001/05/09 acme Fix MODULE_DESC for debug, .bss nitpicks,
+* some cleanups
+* 2000/07/13 acme remove useless #ifdef MODULE and crap
+* #if KERNEL_VERSION > blah
+* 2000/07/06 acme __exit at cyclomx_cleanup
+* 2000/04/02 acme dprintk and cycx_debug
+* module_init/module_exit
+* 2000/01/21 acme rename cyclomx_open to cyclomx_mod_inc_use_count
+* and cyclomx_close to cyclomx_mod_dec_use_count
+* 2000/01/08 acme cleanup
+* 1999/11/06 acme cycx_down back to life (it needs to be
+* called to iounmap the dpmbase)
+* 1999/08/09 acme removed references to enable_tx_int
+* use spinlocks instead of cli/sti in
+* cyclomx_set_state
+* 1999/05/19 acme works directly linked into the kernel
+* init_waitqueue_head for 2.3.* kernel
+* 1999/05/18 acme major cleanup (polling not needed), etc
+* 1998/08/28 acme minor cleanup (ioctls for firmware deleted)
+* queue_task activated
+* 1998/08/08 acme Initial version.
+*/
+
+#include <linux/config.h> /* OS configuration options */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/module.h> /* support for loadable modules */
+#include <linux/ioport.h> /* request_region(), release_region() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/cyclomx.h> /* cyclomx common user API definitions */
+#include <linux/init.h> /* __init (when not using as a module) */
+
+unsigned int cycx_debug;
+
+MODULE_AUTHOR("Arnaldo Carvalho de Melo");
+MODULE_DESCRIPTION("Cyclom 2X Sync Card Driver.");
+MODULE_LICENSE("GPL");
+module_param(cycx_debug, int, 0);
+MODULE_PARM_DESC(cycx_debug, "cyclomx debug level");
+
+/* Defines & Macros */
+
+#define CYCX_DRV_VERSION 0 /* version number */
+#define CYCX_DRV_RELEASE 11 /* release (minor version) number */
+#define CYCX_MAX_CARDS 1 /* max number of adapters */
+
+#define CONFIG_CYCX_CARDS 1
+
+/* Function Prototypes */
+
+/* WAN link driver entry points */
+static int cycx_wan_setup(struct wan_device *wandev, wandev_conf_t *conf);
+static int cycx_wan_shutdown(struct wan_device *wandev);
+
+/* Miscellaneous functions */
+static irqreturn_t cycx_isr(int irq, void *dev_id, struct pt_regs *regs);
+
+/* Global Data
+ * Note: All data must be explicitly initialized!!!
+ */
+
+/* private data */
+static char cycx_drvname[] = "cyclomx";
+static char cycx_fullname[] = "CYCLOM 2X(tm) Sync Card Driver";
+static char cycx_copyright[] = "(c) 1998-2003 Arnaldo Carvalho de Melo "
+ "<acme@conectiva.com.br>";
+static int cycx_ncards = CONFIG_CYCX_CARDS;
+static struct cycx_device *cycx_card_array; /* adapter data space */
+
+/* Kernel Loadable Module Entry Points */
+
+/*
+ * Module 'insert' entry point.
+ * o print announcement
+ * o allocate adapter data space
+ * o initialize static data
+ * o register all cards with WAN router
+ * o calibrate Cyclom 2X shared memory access delay.
+ *
+ * Return: 0 Ok
+ * < 0 error.
+ * Context: process
+ */
+int __init cycx_init(void)
+{
+ int cnt, err = -ENOMEM;
+
+ printk(KERN_INFO "%s v%u.%u %s\n",
+ cycx_fullname, CYCX_DRV_VERSION, CYCX_DRV_RELEASE,
+ cycx_copyright);
+
+ /* Verify number of cards and allocate adapter data space */
+ cycx_ncards = min_t(int, cycx_ncards, CYCX_MAX_CARDS);
+ cycx_ncards = max_t(int, cycx_ncards, 1);
+ cycx_card_array = kmalloc(sizeof(struct cycx_device) * cycx_ncards,
+ GFP_KERNEL);
+ if (!cycx_card_array)
+ goto out;
+
+ memset(cycx_card_array, 0, sizeof(struct cycx_device) * cycx_ncards);
+
+ /* Register adapters with WAN router */
+ for (cnt = 0; cnt < cycx_ncards; ++cnt) {
+ struct cycx_device *card = &cycx_card_array[cnt];
+ struct wan_device *wandev = &card->wandev;
+
+ sprintf(card->devname, "%s%d", cycx_drvname, cnt + 1);
+ wandev->magic = ROUTER_MAGIC;
+ wandev->name = card->devname;
+ wandev->private = card;
+ wandev->setup = cycx_wan_setup;
+ wandev->shutdown = cycx_wan_shutdown;
+ err = register_wan_device(wandev);
+
+ if (err) {
+ printk(KERN_ERR "%s: %s registration failed with "
+ "error %d!\n",
+ cycx_drvname, card->devname, err);
+ break;
+ }
+ }
+
+ err = -ENODEV;
+ if (!cnt) {
+ kfree(cycx_card_array);
+ goto out;
+ }
+ err = 0;
+ cycx_ncards = cnt; /* adjust actual number of cards */
+out: return err;
+}
+
+/*
+ * Module 'remove' entry point.
+ * o unregister all adapters from the WAN router
+ * o release all remaining system resources
+ */
+static void __exit cycx_exit(void)
+{
+ int i = 0;
+
+ for (; i < cycx_ncards; ++i) {
+ struct cycx_device *card = &cycx_card_array[i];
+ unregister_wan_device(card->devname);
+ }
+
+ kfree(cycx_card_array);
+}
+
+/* WAN Device Driver Entry Points */
+/*
+ * Setup/configure WAN link driver.
+ * o check adapter state
+ * o make sure firmware is present in configuration
+ * o allocate interrupt vector
+ * o setup Cyclom 2X hardware
+ * o call appropriate routine to perform protocol-specific initialization
+ *
+ * This function is called when router handles ROUTER_SETUP IOCTL. The
+ * configuration structure is in kernel memory (including extended data, if
+ * any).
+ */
+static int cycx_wan_setup(struct wan_device *wandev, wandev_conf_t *conf)
+{
+ int rc = -EFAULT;
+ struct cycx_device *card;
+ int irq;
+
+ /* Sanity checks */
+
+ if (!wandev || !wandev->private || !conf)
+ goto out;
+
+ card = wandev->private;
+ rc = -EBUSY;
+ if (wandev->state != WAN_UNCONFIGURED)
+ goto out;
+
+ rc = -EINVAL;
+ if (!conf->data_size || !conf->data) {
+ printk(KERN_ERR "%s: firmware not found in configuration "
+ "data!\n", wandev->name);
+ goto out;
+ }
+
+ if (conf->irq <= 0) {
+ printk(KERN_ERR "%s: can't configure without IRQ!\n",
+ wandev->name);
+ goto out;
+ }
+
+ /* Allocate IRQ */
+ irq = conf->irq == 2 ? 9 : conf->irq; /* IRQ2 -> IRQ9 */
+
+ if (request_irq(irq, cycx_isr, 0, wandev->name, card)) {
+ printk(KERN_ERR "%s: can't reserve IRQ %d!\n",
+ wandev->name, irq);
+ goto out;
+ }
+
+ /* Configure hardware, load firmware, etc. */
+ memset(&card->hw, 0, sizeof(card->hw));
+ card->hw.irq = irq;
+ card->hw.dpmsize = CYCX_WINDOWSIZE;
+ card->hw.fwid = CFID_X25_2X;
+ spin_lock_init(&card->lock);
+ init_waitqueue_head(&card->wait_stats);
+
+ rc = cycx_setup(&card->hw, conf->data, conf->data_size, conf->maddr);
+ if (rc)
+ goto out_irq;
+
+ /* Initialize WAN device data space */
+ wandev->irq = irq;
+ wandev->dma = wandev->ioport = 0;
+ wandev->maddr = (unsigned long)card->hw.dpmbase;
+ wandev->msize = card->hw.dpmsize;
+ wandev->hw_opt[2] = 0;
+ wandev->hw_opt[3] = card->hw.fwid;
+
+ /* Protocol-specific initialization */
+ switch (card->hw.fwid) {
+#ifdef CONFIG_CYCLOMX_X25
+ case CFID_X25_2X:
+ rc = cycx_x25_wan_init(card, conf);
+ break;
+#endif
+ default:
+ printk(KERN_ERR "%s: this firmware is not supported!\n",
+ wandev->name);
+ rc = -EINVAL;
+ }
+
+ if (rc) {
+ cycx_down(&card->hw);
+ goto out_irq;
+ }
+
+ rc = 0;
+out:
+ return rc;
+out_irq:
+ free_irq(irq, card);
+ goto out;
+}
+
+/*
+ * Shut down WAN link driver.
+ * o shut down adapter hardware
+ * o release system resources.
+ *
+ * This function is called by the router when device is being unregistered or
+ * when it handles ROUTER_DOWN IOCTL.
+ */
+static int cycx_wan_shutdown(struct wan_device *wandev)
+{
+ int ret = -EFAULT;
+ struct cycx_device *card;
+
+ /* sanity checks */
+ if (!wandev || !wandev->private)
+ goto out;
+
+ ret = 0;
+ if (wandev->state == WAN_UNCONFIGURED)
+ goto out;
+
+ card = wandev->private;
+ wandev->state = WAN_UNCONFIGURED;
+ cycx_down(&card->hw);
+ printk(KERN_INFO "%s: irq %d being freed!\n", wandev->name,
+ wandev->irq);
+ free_irq(wandev->irq, card);
+out: return ret;
+}
+
+/* Miscellaneous */
+/*
+ * Cyclom 2X Interrupt Service Routine.
+ * o acknowledge Cyclom 2X hardware interrupt.
+ * o call protocol-specific interrupt service routine, if any.
+ */
+static irqreturn_t cycx_isr(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct cycx_device *card = (struct cycx_device *)dev_id;
+
+ if (!card || card->wandev.state == WAN_UNCONFIGURED)
+ goto out;
+
+ if (card->in_isr) {
+ printk(KERN_WARNING "%s: interrupt re-entrancy on IRQ %d!\n",
+ card->devname, card->wandev.irq);
+ goto out;
+ }
+
+ if (card->isr)
+ card->isr(card);
+ return IRQ_HANDLED;
+out:
+ return IRQ_NONE;
+}
+
+/* Set WAN device state. */
+void cycx_set_state(struct cycx_device *card, int state)
+{
+ unsigned long flags;
+ char *string_state = NULL;
+
+ spin_lock_irqsave(&card->lock, flags);
+
+ if (card->wandev.state != state) {
+ switch (state) {
+ case WAN_CONNECTED:
+ string_state = "connected!";
+ break;
+ case WAN_DISCONNECTED:
+ string_state = "disconnected!";
+ break;
+ }
+ printk(KERN_INFO "%s: link %s\n", card->devname, string_state);
+ card->wandev.state = state;
+ }
+
+ card->state_tick = jiffies;
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+module_init(cycx_init);
+module_exit(cycx_exit);
diff --git a/drivers/net/wan/cycx_x25.c b/drivers/net/wan/cycx_x25.c
new file mode 100644
index 00000000000..5b48cd8568f
--- /dev/null
+++ b/drivers/net/wan/cycx_x25.c
@@ -0,0 +1,1609 @@
+/*
+* cycx_x25.c Cyclom 2X WAN Link Driver. X.25 module.
+*
+* Author: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+*
+* Copyright: (c) 1998-2003 Arnaldo Carvalho de Melo
+*
+* Based on sdla_x25.c by Gene Kozin <genek@compuserve.com>
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* 2001/01/12 acme use dev_kfree_skb_irq on interrupt context
+* 2000/04/02 acme dprintk, cycx_debug
+* fixed the bug introduced in get_dev_by_lcn and
+* get_dev_by_dte_addr by the anonymous hacker
+* that converted this driver to softnet
+* 2000/01/08 acme cleanup
+* 1999/10/27 acme use ARPHRD_HWX25 so that the X.25 stack know
+* that we have a X.25 stack implemented in
+* firmware onboard
+* 1999/10/18 acme support for X.25 sockets in if_send,
+* beware: socket(AF_X25...) IS WORK IN PROGRESS,
+* TCP/IP over X.25 via wanrouter not affected,
+* working.
+* 1999/10/09 acme chan_disc renamed to chan_disconnect,
+* began adding support for X.25 sockets:
+* conf->protocol in new_if
+* 1999/10/05 acme fixed return E... to return -E...
+* 1999/08/10 acme serialized access to the card thru a spinlock
+* in x25_exec
+* 1999/08/09 acme removed per channel spinlocks
+* removed references to enable_tx_int
+* 1999/05/28 acme fixed nibble_to_byte, ackvc now properly treated
+* if_send simplified
+* 1999/05/25 acme fixed t1, t2, t21 & t23 configuration
+* use spinlocks instead of cli/sti in some points
+* 1999/05/24 acme finished the x25_get_stat function
+* 1999/05/23 acme dev->type = ARPHRD_X25 (tcpdump only works,
+* AFAIT, with ARPHRD_ETHER). This seems to be
+* needed to use socket(AF_X25)...
+* Now the config file must specify a peer media
+* address for svc channels over a crossover cable.
+* Removed hold_timeout from x25_channel_t,
+* not used.
+* A little enhancement in the DEBUG processing
+* 1999/05/22 acme go to DISCONNECTED in disconnect_confirm_intr,
+* instead of chan_disc.
+* 1999/05/16 marcelo fixed timer initialization in SVCs
+* 1999/01/05 acme x25_configure now get (most of) all
+* parameters...
+* 1999/01/05 acme pktlen now (correctly) uses log2 (value
+* configured)
+* 1999/01/03 acme judicious use of data types (u8, u16, u32, etc)
+* 1999/01/03 acme cyx_isr: reset dpmbase to acknowledge
+* indication (interrupt from cyclom 2x)
+* 1999/01/02 acme cyx_isr: first hackings...
+* 1999/01/0203 acme when initializing an array don't give less
+* elements than declared...
+* example: char send_cmd[6] = "?\xFF\x10";
+* you'll gonna lose a couple hours, 'cause your
+* brain won't admit that there's an error in the
+* above declaration... the side effect is that
+* memset is put into the unresolved symbols
+* instead of using the inline memset functions...
+* 1999/01/02 acme began chan_connect, chan_send, x25_send
+* 1998/12/31 acme x25_configure
+* this code can be compiled as non module
+* 1998/12/27 acme code cleanup
+* IPX code wiped out! let's decrease code
+* complexity for now, remember: I'm learning! :)
+* bps_to_speed_code OK
+* 1998/12/26 acme Minimal debug code cleanup
+* 1998/08/08 acme Initial version.
+*/
+
+#define CYCLOMX_X25_DEBUG 1
+
+#include <linux/errno.h> /* return codes */
+#include <linux/if_arp.h> /* ARPHRD_HWX25 */
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/module.h>
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/wanrouter.h> /* WAN router definitions */
+
+#include <asm/byteorder.h> /* htons(), etc. */
+
+#include <linux/cyclomx.h> /* Cyclom 2X common user API definitions */
+#include <linux/cycx_x25.h> /* X.25 firmware API definitions */
+
+#include <net/x25device.h>
+
+/* Defines & Macros */
+#define CYCX_X25_MAX_CMD_RETRY 5
+#define CYCX_X25_CHAN_MTU 2048 /* unfragmented logical channel MTU */
+
+/* Data Structures */
+/* This is an extension of the 'struct net_device' we create for each network
+ interface to keep the rest of X.25 channel-specific data. */
+struct cycx_x25_channel {
+ /* This member must be first. */
+ struct net_device *slave; /* WAN slave */
+
+ char name[WAN_IFNAME_SZ+1]; /* interface name, ASCIIZ */
+ char addr[WAN_ADDRESS_SZ+1]; /* media address, ASCIIZ */
+ char *local_addr; /* local media address, ASCIIZ -
+ svc thru crossover cable */
+ s16 lcn; /* logical channel number/conn.req.key*/
+ u8 link;
+ struct timer_list timer; /* timer used for svc channel disc. */
+ u16 protocol; /* ethertype, 0 - multiplexed */
+ u8 svc; /* 0 - permanent, 1 - switched */
+ u8 state; /* channel state */
+ u8 drop_sequence; /* mark sequence for dropping */
+ u32 idle_tmout; /* sec, before disconnecting */
+ struct sk_buff *rx_skb; /* receive socket buffer */
+ struct cycx_device *card; /* -> owner */
+ struct net_device_stats ifstats;/* interface statistics */
+};
+
+/* Function Prototypes */
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int cycx_wan_update(struct wan_device *wandev),
+ cycx_wan_new_if(struct wan_device *wandev, struct net_device *dev,
+ wanif_conf_t *conf),
+ cycx_wan_del_if(struct wan_device *wandev, struct net_device *dev);
+
+/* Network device interface */
+static int cycx_netdevice_init(struct net_device *dev),
+ cycx_netdevice_open(struct net_device *dev),
+ cycx_netdevice_stop(struct net_device *dev),
+ cycx_netdevice_hard_header(struct sk_buff *skb,
+ struct net_device *dev, u16 type,
+ void *daddr, void *saddr, unsigned len),
+ cycx_netdevice_rebuild_header(struct sk_buff *skb),
+ cycx_netdevice_hard_start_xmit(struct sk_buff *skb,
+ struct net_device *dev);
+
+static struct net_device_stats *
+ cycx_netdevice_get_stats(struct net_device *dev);
+
+/* Interrupt handlers */
+static void cycx_x25_irq_handler(struct cycx_device *card),
+ cycx_x25_irq_tx(struct cycx_device *card, struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_rx(struct cycx_device *card, struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_log(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_stat(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_connect_confirm(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_disconnect_confirm(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_connect(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_disconnect(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd),
+ cycx_x25_irq_spurious(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd);
+
+/* X.25 firmware interface functions */
+static int cycx_x25_configure(struct cycx_device *card,
+ struct cycx_x25_config *conf),
+ cycx_x25_get_stats(struct cycx_device *card),
+ cycx_x25_send(struct cycx_device *card, u8 link, u8 lcn, u8 bitm,
+ int len, void *buf),
+ cycx_x25_connect_response(struct cycx_device *card,
+ struct cycx_x25_channel *chan),
+ cycx_x25_disconnect_response(struct cycx_device *card, u8 link,
+ u8 lcn);
+
+/* channel functions */
+static int cycx_x25_chan_connect(struct net_device *dev),
+ cycx_x25_chan_send(struct net_device *dev, struct sk_buff *skb);
+
+static void cycx_x25_chan_disconnect(struct net_device *dev),
+ cycx_x25_chan_send_event(struct net_device *dev, u8 event);
+
+/* Miscellaneous functions */
+static void cycx_x25_set_chan_state(struct net_device *dev, u8 state),
+ cycx_x25_chan_timer(unsigned long d);
+
+static void nibble_to_byte(u8 *s, u8 *d, u8 len, u8 nibble),
+ reset_timer(struct net_device *dev);
+
+static u8 bps_to_speed_code(u32 bps);
+static u8 cycx_log2(u32 n);
+
+static unsigned dec_to_uint(u8 *str, int len);
+
+static struct net_device *cycx_x25_get_dev_by_lcn(struct wan_device *wandev,
+ s16 lcn);
+static struct net_device *
+ cycx_x25_get_dev_by_dte_addr(struct wan_device *wandev, char *dte);
+
+#ifdef CYCLOMX_X25_DEBUG
+static void hex_dump(char *msg, unsigned char *p, int len);
+static void cycx_x25_dump_config(struct cycx_x25_config *conf);
+static void cycx_x25_dump_stats(struct cycx_x25_stats *stats);
+static void cycx_x25_dump_devs(struct wan_device *wandev);
+#else
+#define hex_dump(msg, p, len)
+#define cycx_x25_dump_config(conf)
+#define cycx_x25_dump_stats(stats)
+#define cycx_x25_dump_devs(wandev)
+#endif
+/* Public Functions */
+
+/* X.25 Protocol Initialization routine.
+ *
+ * This routine is called by the main Cyclom 2X module during setup. At this
+ * point adapter is completely initialized and X.25 firmware is running.
+ * o configure adapter
+ * o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return: 0 o.k.
+ * < 0 failure. */
+int cycx_x25_wan_init(struct cycx_device *card, wandev_conf_t *conf)
+{
+ struct cycx_x25_config cfg;
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_X25) {
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id);
+ return -EINVAL;
+ }
+
+ /* Initialize protocol-specific fields */
+ card->mbox = card->hw.dpmbase + X25_MBOX_OFFS;
+ card->u.x.connection_keys = 0;
+ spin_lock_init(&card->u.x.lock);
+
+ /* Configure adapter. Here we set reasonable defaults, then parse
+ * device configuration structure and set configuration options.
+ * Most configuration options are verified and corrected (if
+ * necessary) since we can't rely on the adapter to do so and don't
+ * want it to fail either. */
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.link = 0;
+ cfg.clock = conf->clocking == WANOPT_EXTERNAL ? 8 : 55;
+ cfg.speed = bps_to_speed_code(conf->bps);
+ cfg.n3win = 7;
+ cfg.n2win = 2;
+ cfg.n2 = 5;
+ cfg.nvc = 1;
+ cfg.npvc = 1;
+ cfg.flags = 0x02; /* default = V35 */
+ cfg.t1 = 10; /* line carrier timeout */
+ cfg.t2 = 29; /* tx timeout */
+ cfg.t21 = 180; /* CALL timeout */
+ cfg.t23 = 180; /* CLEAR timeout */
+
+ /* adjust MTU */
+ if (!conf->mtu || conf->mtu >= 512)
+ card->wandev.mtu = 512;
+ else if (conf->mtu >= 256)
+ card->wandev.mtu = 256;
+ else if (conf->mtu >= 128)
+ card->wandev.mtu = 128;
+ else
+ card->wandev.mtu = 64;
+
+ cfg.pktlen = cycx_log2(card->wandev.mtu);
+
+ if (conf->station == WANOPT_DTE) {
+ cfg.locaddr = 3; /* DTE */
+ cfg.remaddr = 1; /* DCE */
+ } else {
+ cfg.locaddr = 1; /* DCE */
+ cfg.remaddr = 3; /* DTE */
+ }
+
+ if (conf->interface == WANOPT_RS232)
+ cfg.flags = 0; /* FIXME just reset the 2nd bit */
+
+ if (conf->u.x25.hi_pvc) {
+ card->u.x.hi_pvc = min_t(unsigned int, conf->u.x25.hi_pvc, 4095);
+ card->u.x.lo_pvc = min_t(unsigned int, conf->u.x25.lo_pvc, card->u.x.hi_pvc);
+ }
+
+ if (conf->u.x25.hi_svc) {
+ card->u.x.hi_svc = min_t(unsigned int, conf->u.x25.hi_svc, 4095);
+ card->u.x.lo_svc = min_t(unsigned int, conf->u.x25.lo_svc, card->u.x.hi_svc);
+ }
+
+ if (card->u.x.lo_pvc == 255)
+ cfg.npvc = 0;
+ else
+ cfg.npvc = card->u.x.hi_pvc - card->u.x.lo_pvc + 1;
+
+ cfg.nvc = card->u.x.hi_svc - card->u.x.lo_svc + 1 + cfg.npvc;
+
+ if (conf->u.x25.hdlc_window)
+ cfg.n2win = min_t(unsigned int, conf->u.x25.hdlc_window, 7);
+
+ if (conf->u.x25.pkt_window)
+ cfg.n3win = min_t(unsigned int, conf->u.x25.pkt_window, 7);
+
+ if (conf->u.x25.t1)
+ cfg.t1 = min_t(unsigned int, conf->u.x25.t1, 30);
+
+ if (conf->u.x25.t2)
+ cfg.t2 = min_t(unsigned int, conf->u.x25.t2, 30);
+
+ if (conf->u.x25.t11_t21)
+ cfg.t21 = min_t(unsigned int, conf->u.x25.t11_t21, 30);
+
+ if (conf->u.x25.t13_t23)
+ cfg.t23 = min_t(unsigned int, conf->u.x25.t13_t23, 30);
+
+ if (conf->u.x25.n2)
+ cfg.n2 = min_t(unsigned int, conf->u.x25.n2, 30);
+
+ /* initialize adapter */
+ if (cycx_x25_configure(card, &cfg))
+ return -EIO;
+
+ /* Initialize protocol-specific fields of adapter data space */
+ card->wandev.bps = conf->bps;
+ card->wandev.interface = conf->interface;
+ card->wandev.clocking = conf->clocking;
+ card->wandev.station = conf->station;
+ card->isr = cycx_x25_irq_handler;
+ card->exec = NULL;
+ card->wandev.update = cycx_wan_update;
+ card->wandev.new_if = cycx_wan_new_if;
+ card->wandev.del_if = cycx_wan_del_if;
+ card->wandev.state = WAN_DISCONNECTED;
+
+ return 0;
+}
+
+/* WAN Device Driver Entry Points */
+/* Update device status & statistics. */
+static int cycx_wan_update(struct wan_device *wandev)
+{
+ /* sanity checks */
+ if (!wandev || !wandev->private)
+ return -EFAULT;
+
+ if (wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ cycx_x25_get_stats(wandev->private);
+
+ return 0;
+}
+
+/* Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registration.
+ *
+ * Return: 0 o.k.
+ * < 0 failure (channel will not be created) */
+static int cycx_wan_new_if(struct wan_device *wandev, struct net_device *dev,
+ wanif_conf_t *conf)
+{
+ struct cycx_device *card = wandev->private;
+ struct cycx_x25_channel *chan;
+ int err = 0;
+
+ if (!conf->name[0] || strlen(conf->name) > WAN_IFNAME_SZ) {
+ printk(KERN_INFO "%s: invalid interface name!\n",
+ card->devname);
+ return -EINVAL;
+ }
+
+ /* allocate and initialize private data */
+ chan = kmalloc(sizeof(struct cycx_x25_channel), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ memset(chan, 0, sizeof(*chan));
+ strcpy(chan->name, conf->name);
+ chan->card = card;
+ chan->link = conf->port;
+ chan->protocol = conf->protocol ? ETH_P_X25 : ETH_P_IP;
+ chan->rx_skb = NULL;
+ /* only used in svc connected thru crossover cable */
+ chan->local_addr = NULL;
+
+ if (conf->addr[0] == '@') { /* SVC */
+ int len = strlen(conf->local_addr);
+
+ if (len) {
+ if (len > WAN_ADDRESS_SZ) {
+ printk(KERN_ERR "%s: %s local addr too long!\n",
+ wandev->name, chan->name);
+ kfree(chan);
+ return -EINVAL;
+ } else {
+ chan->local_addr = kmalloc(len + 1, GFP_KERNEL);
+
+ if (!chan->local_addr) {
+ kfree(chan);
+ return -ENOMEM;
+ }
+ }
+
+ strncpy(chan->local_addr, conf->local_addr,
+ WAN_ADDRESS_SZ);
+ }
+
+ chan->svc = 1;
+ strncpy(chan->addr, &conf->addr[1], WAN_ADDRESS_SZ);
+ init_timer(&chan->timer);
+ chan->timer.function = cycx_x25_chan_timer;
+ chan->timer.data = (unsigned long)dev;
+
+ /* Set channel timeouts (default if not specified) */
+ chan->idle_tmout = conf->idle_timeout ? conf->idle_timeout : 90;
+ } else if (is_digit(conf->addr[0])) { /* PVC */
+ s16 lcn = dec_to_uint(conf->addr, 0);
+
+ if (lcn >= card->u.x.lo_pvc && lcn <= card->u.x.hi_pvc)
+ chan->lcn = lcn;
+ else {
+ printk(KERN_ERR
+ "%s: PVC %u is out of range on interface %s!\n",
+ wandev->name, lcn, chan->name);
+ err = -EINVAL;
+ }
+ } else {
+ printk(KERN_ERR "%s: invalid media address on interface %s!\n",
+ wandev->name, chan->name);
+ err = -EINVAL;
+ }
+
+ if (err) {
+ if (chan->local_addr)
+ kfree(chan->local_addr);
+
+ kfree(chan);
+ return err;
+ }
+
+ /* prepare network device data space for registration */
+ strcpy(dev->name, chan->name);
+ dev->init = cycx_netdevice_init;
+ dev->priv = chan;
+
+ return 0;
+}
+
+/* Delete logical channel. */
+static int cycx_wan_del_if(struct wan_device *wandev, struct net_device *dev)
+{
+ if (dev->priv) {
+ struct cycx_x25_channel *chan = dev->priv;
+
+ if (chan->svc) {
+ if (chan->local_addr)
+ kfree(chan->local_addr);
+
+ if (chan->state == WAN_CONNECTED)
+ del_timer(&chan->timer);
+ }
+
+ kfree(chan);
+ dev->priv = NULL;
+ }
+
+ return 0;
+}
+
+/* Network Device Interface */
+/* Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration. Returning anything but zero will fail interface
+ * registration. */
+static int cycx_netdevice_init(struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+ struct cycx_device *card = chan->card;
+ struct wan_device *wandev = &card->wandev;
+
+ /* Initialize device driver entry points */
+ dev->open = cycx_netdevice_open;
+ dev->stop = cycx_netdevice_stop;
+ dev->hard_header = cycx_netdevice_hard_header;
+ dev->rebuild_header = cycx_netdevice_rebuild_header;
+ dev->hard_start_xmit = cycx_netdevice_hard_start_xmit;
+ dev->get_stats = cycx_netdevice_get_stats;
+
+ /* Initialize media-specific parameters */
+ dev->mtu = CYCX_X25_CHAN_MTU;
+ dev->type = ARPHRD_HWX25; /* ARP h/w type */
+ dev->hard_header_len = 0; /* media header length */
+ dev->addr_len = 0; /* hardware address length */
+
+ if (!chan->svc)
+ *(u16*)dev->dev_addr = htons(chan->lcn);
+
+ /* Initialize hardware parameters (just for reference) */
+ dev->irq = wandev->irq;
+ dev->dma = wandev->dma;
+ dev->base_addr = wandev->ioport;
+ dev->mem_start = (unsigned long)wandev->maddr;
+ dev->mem_end = (unsigned long)(wandev->maddr +
+ wandev->msize - 1);
+ dev->flags |= IFF_NOARP;
+
+ /* Set transmit buffer queue length */
+ dev->tx_queue_len = 10;
+ SET_MODULE_OWNER(dev);
+
+ /* Initialize socket buffers */
+ cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+
+ return 0;
+}
+
+/* Open network interface.
+ * o prevent module from unloading by incrementing use count
+ * o if link is disconnected then initiate connection
+ *
+ * Return 0 if O.k. or errno. */
+static int cycx_netdevice_open(struct net_device *dev)
+{
+ if (netif_running(dev))
+ return -EBUSY; /* only one open is allowed */
+
+ netif_start_queue(dev);
+ return 0;
+}
+
+/* Close network interface.
+ * o reset flags.
+ * o if there's no more open channels then disconnect physical link. */
+static int cycx_netdevice_stop(struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+
+ netif_stop_queue(dev);
+
+ if (chan->state == WAN_CONNECTED || chan->state == WAN_CONNECTING)
+ cycx_x25_chan_disconnect(dev);
+
+ return 0;
+}
+
+/* Build media header.
+ * o encapsulate packet according to encapsulation type.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol' field of
+ * the socket buffer, so that we don't forget it. If encapsulation fails,
+ * set skb->protocol to 0 and discard packet later.
+ *
+ * Return: media header length. */
+static int cycx_netdevice_hard_header(struct sk_buff *skb,
+ struct net_device *dev, u16 type,
+ void *daddr, void *saddr, unsigned len)
+{
+ skb->protocol = type;
+
+ return dev->hard_header_len;
+}
+
+/* * Re-build media header.
+ * Return: 1 physical address resolved.
+ * 0 physical address not resolved */
+static int cycx_netdevice_rebuild_header(struct sk_buff *skb)
+{
+ return 1;
+}
+
+/* Send a packet on a network interface.
+ * o set busy flag (marks start of the transmission).
+ * o check link state. If link is not up, then drop the packet.
+ * o check channel status. If it's down then initiate a call.
+ * o pass a packet to corresponding WAN device.
+ * o free socket buffer
+ *
+ * Return: 0 complete (socket buffer must be freed)
+ * non-0 packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ * bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ * protocol stack and can be used for flow control with protocol layer. */
+static int cycx_netdevice_hard_start_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+ struct cycx_device *card = chan->card;
+
+ if (!chan->svc)
+ chan->protocol = skb->protocol;
+
+ if (card->wandev.state != WAN_CONNECTED)
+ ++chan->ifstats.tx_dropped;
+ else if (chan->svc && chan->protocol &&
+ chan->protocol != skb->protocol) {
+ printk(KERN_INFO
+ "%s: unsupported Ethertype 0x%04X on interface %s!\n",
+ card->devname, skb->protocol, dev->name);
+ ++chan->ifstats.tx_errors;
+ } else if (chan->protocol == ETH_P_IP) {
+ switch (chan->state) {
+ case WAN_DISCONNECTED:
+ if (cycx_x25_chan_connect(dev)) {
+ netif_stop_queue(dev);
+ return -EBUSY;
+ }
+ /* fall thru */
+ case WAN_CONNECTED:
+ reset_timer(dev);
+ dev->trans_start = jiffies;
+ netif_stop_queue(dev);
+
+ if (cycx_x25_chan_send(dev, skb))
+ return -EBUSY;
+
+ break;
+ default:
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ }
+ } else { /* chan->protocol == ETH_P_X25 */
+ switch (skb->data[0]) {
+ case 0: break;
+ case 1: /* Connect request */
+ cycx_x25_chan_connect(dev);
+ goto free_packet;
+ case 2: /* Disconnect request */
+ cycx_x25_chan_disconnect(dev);
+ goto free_packet;
+ default:
+ printk(KERN_INFO
+ "%s: unknown %d x25-iface request on %s!\n",
+ card->devname, skb->data[0], dev->name);
+ ++chan->ifstats.tx_errors;
+ goto free_packet;
+ }
+
+ skb_pull(skb, 1); /* Remove control byte */
+ reset_timer(dev);
+ dev->trans_start = jiffies;
+ netif_stop_queue(dev);
+
+ if (cycx_x25_chan_send(dev, skb)) {
+ /* prepare for future retransmissions */
+ skb_push(skb, 1);
+ return -EBUSY;
+ }
+ }
+
+free_packet:
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/* Get Ethernet-style interface statistics.
+ * Return a pointer to struct net_device_stats */
+static struct net_device_stats *cycx_netdevice_get_stats(struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+
+ return chan ? &chan->ifstats : NULL;
+}
+
+/* Interrupt Handlers */
+/* X.25 Interrupt Service Routine. */
+static void cycx_x25_irq_handler(struct cycx_device *card)
+{
+ struct cycx_x25_cmd cmd;
+ u16 z = 0;
+
+ card->in_isr = 1;
+ card->buff_int_mode_unbusy = 0;
+ cycx_peek(&card->hw, X25_RXMBOX_OFFS, &cmd, sizeof(cmd));
+
+ switch (cmd.command) {
+ case X25_DATA_INDICATION:
+ cycx_x25_irq_rx(card, &cmd);
+ break;
+ case X25_ACK_FROM_VC:
+ cycx_x25_irq_tx(card, &cmd);
+ break;
+ case X25_LOG:
+ cycx_x25_irq_log(card, &cmd);
+ break;
+ case X25_STATISTIC:
+ cycx_x25_irq_stat(card, &cmd);
+ break;
+ case X25_CONNECT_CONFIRM:
+ cycx_x25_irq_connect_confirm(card, &cmd);
+ break;
+ case X25_CONNECT_INDICATION:
+ cycx_x25_irq_connect(card, &cmd);
+ break;
+ case X25_DISCONNECT_INDICATION:
+ cycx_x25_irq_disconnect(card, &cmd);
+ break;
+ case X25_DISCONNECT_CONFIRM:
+ cycx_x25_irq_disconnect_confirm(card, &cmd);
+ break;
+ case X25_LINE_ON:
+ cycx_set_state(card, WAN_CONNECTED);
+ break;
+ case X25_LINE_OFF:
+ cycx_set_state(card, WAN_DISCONNECTED);
+ break;
+ default:
+ cycx_x25_irq_spurious(card, &cmd);
+ break;
+ }
+
+ cycx_poke(&card->hw, 0, &z, sizeof(z));
+ cycx_poke(&card->hw, X25_RXMBOX_OFFS, &z, sizeof(z));
+ card->in_isr = 0;
+}
+
+/* Transmit interrupt handler.
+ * o Release socket buffer
+ * o Clear 'tbusy' flag */
+static void cycx_x25_irq_tx(struct cycx_device *card, struct cycx_x25_cmd *cmd)
+{
+ struct net_device *dev;
+ struct wan_device *wandev = &card->wandev;
+ u8 lcn;
+
+ cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+
+ /* unbusy device and then dev_tint(); */
+ dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+ if (dev) {
+ card->buff_int_mode_unbusy = 1;
+ netif_wake_queue(dev);
+ } else
+ printk(KERN_ERR "%s:ackvc for inexistent lcn %d\n",
+ card->devname, lcn);
+}
+
+/* Receive interrupt handler.
+ * This routine handles fragmented IP packets using M-bit according to the
+ * RFC1356.
+ * o map logical channel number to network interface.
+ * o allocate socket buffer or append received packet to the existing one.
+ * o if M-bit is reset (i.e. it's the last packet in a sequence) then
+ * decapsulate packet and pass socket buffer to the protocol stack.
+ *
+ * Notes:
+ * 1. When allocating a socket buffer, if M-bit is set then more data is
+ * coming and we have to allocate buffer for the maximum IP packet size
+ * expected on this channel.
+ * 2. If something goes wrong and X.25 packet has to be dropped (e.g. no
+ * socket buffers available) the whole packet sequence must be discarded. */
+static void cycx_x25_irq_rx(struct cycx_device *card, struct cycx_x25_cmd *cmd)
+{
+ struct wan_device *wandev = &card->wandev;
+ struct net_device *dev;
+ struct cycx_x25_channel *chan;
+ struct sk_buff *skb;
+ u8 bitm, lcn;
+ int pktlen = cmd->len - 5;
+
+ cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+ cycx_peek(&card->hw, cmd->buf + 4, &bitm, sizeof(bitm));
+ bitm &= 0x10;
+
+ dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+ if (!dev) {
+ /* Invalid channel, discard packet */
+ printk(KERN_INFO "%s: receiving on orphaned LCN %d!\n",
+ card->devname, lcn);
+ return;
+ }
+
+ chan = dev->priv;
+ reset_timer(dev);
+
+ if (chan->drop_sequence) {
+ if (!bitm)
+ chan->drop_sequence = 0;
+ else
+ return;
+ }
+
+ if ((skb = chan->rx_skb) == NULL) {
+ /* Allocate new socket buffer */
+ int bufsize = bitm ? dev->mtu : pktlen;
+
+ if ((skb = dev_alloc_skb((chan->protocol == ETH_P_X25 ? 1 : 0) +
+ bufsize +
+ dev->hard_header_len)) == NULL) {
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ chan->drop_sequence = 1;
+ ++chan->ifstats.rx_dropped;
+ return;
+ }
+
+ if (chan->protocol == ETH_P_X25) /* X.25 socket layer control */
+ /* 0 = data packet (dev_alloc_skb zeroed skb->data) */
+ skb_put(skb, 1);
+
+ skb->dev = dev;
+ skb->protocol = htons(chan->protocol);
+ chan->rx_skb = skb;
+ }
+
+ if (skb_tailroom(skb) < pktlen) {
+ /* No room for the packet. Call off the whole thing! */
+ dev_kfree_skb_irq(skb);
+ chan->rx_skb = NULL;
+
+ if (bitm)
+ chan->drop_sequence = 1;
+
+ printk(KERN_INFO "%s: unexpectedly long packet sequence "
+ "on interface %s!\n", card->devname, dev->name);
+ ++chan->ifstats.rx_length_errors;
+ return;
+ }
+
+ /* Append packet to the socket buffer */
+ cycx_peek(&card->hw, cmd->buf + 5, skb_put(skb, pktlen), pktlen);
+
+ if (bitm)
+ return; /* more data is coming */
+
+ chan->rx_skb = NULL; /* dequeue packet */
+
+ ++chan->ifstats.rx_packets;
+ chan->ifstats.rx_bytes += pktlen;
+
+ skb->mac.raw = skb->data;
+ netif_rx(skb);
+ dev->last_rx = jiffies; /* timestamp */
+}
+
+/* Connect interrupt handler. */
+static void cycx_x25_irq_connect(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd)
+{
+ struct wan_device *wandev = &card->wandev;
+ struct net_device *dev = NULL;
+ struct cycx_x25_channel *chan;
+ u8 d[32],
+ loc[24],
+ rem[24];
+ u8 lcn, sizeloc, sizerem;
+
+ cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+ cycx_peek(&card->hw, cmd->buf + 5, &sizeloc, sizeof(sizeloc));
+ cycx_peek(&card->hw, cmd->buf + 6, d, cmd->len - 6);
+
+ sizerem = sizeloc >> 4;
+ sizeloc &= 0x0F;
+
+ loc[0] = rem[0] = '\0';
+
+ if (sizeloc)
+ nibble_to_byte(d, loc, sizeloc, 0);
+
+ if (sizerem)
+ nibble_to_byte(d + (sizeloc >> 1), rem, sizerem, sizeloc & 1);
+
+ dprintk(1, KERN_INFO "%s:lcn=%d, local=%s, remote=%s\n",
+ __FUNCTION__, lcn, loc, rem);
+
+ dev = cycx_x25_get_dev_by_dte_addr(wandev, rem);
+ if (!dev) {
+ /* Invalid channel, discard packet */
+ printk(KERN_INFO "%s: connect not expected: remote %s!\n",
+ card->devname, rem);
+ return;
+ }
+
+ chan = dev->priv;
+ chan->lcn = lcn;
+ cycx_x25_connect_response(card, chan);
+ cycx_x25_set_chan_state(dev, WAN_CONNECTED);
+}
+
+/* Connect confirm interrupt handler. */
+static void cycx_x25_irq_connect_confirm(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd)
+{
+ struct wan_device *wandev = &card->wandev;
+ struct net_device *dev;
+ struct cycx_x25_channel *chan;
+ u8 lcn, key;
+
+ cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+ cycx_peek(&card->hw, cmd->buf + 1, &key, sizeof(key));
+ dprintk(1, KERN_INFO "%s: %s:lcn=%d, key=%d\n",
+ card->devname, __FUNCTION__, lcn, key);
+
+ dev = cycx_x25_get_dev_by_lcn(wandev, -key);
+ if (!dev) {
+ /* Invalid channel, discard packet */
+ clear_bit(--key, (void*)&card->u.x.connection_keys);
+ printk(KERN_INFO "%s: connect confirm not expected: lcn %d, "
+ "key=%d!\n", card->devname, lcn, key);
+ return;
+ }
+
+ clear_bit(--key, (void*)&card->u.x.connection_keys);
+ chan = dev->priv;
+ chan->lcn = lcn;
+ cycx_x25_set_chan_state(dev, WAN_CONNECTED);
+}
+
+/* Disconnect confirm interrupt handler. */
+static void cycx_x25_irq_disconnect_confirm(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd)
+{
+ struct wan_device *wandev = &card->wandev;
+ struct net_device *dev;
+ u8 lcn;
+
+ cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+ dprintk(1, KERN_INFO "%s: %s:lcn=%d\n",
+ card->devname, __FUNCTION__, lcn);
+ dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+ if (!dev) {
+ /* Invalid channel, discard packet */
+ printk(KERN_INFO "%s:disconnect confirm not expected!:lcn %d\n",
+ card->devname, lcn);
+ return;
+ }
+
+ cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+}
+
+/* disconnect interrupt handler. */
+static void cycx_x25_irq_disconnect(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd)
+{
+ struct wan_device *wandev = &card->wandev;
+ struct net_device *dev;
+ u8 lcn;
+
+ cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn));
+ dprintk(1, KERN_INFO "%s:lcn=%d\n", __FUNCTION__, lcn);
+
+ dev = cycx_x25_get_dev_by_lcn(wandev, lcn);
+ if (dev) {
+ struct cycx_x25_channel *chan = dev->priv;
+
+ cycx_x25_disconnect_response(card, chan->link, lcn);
+ cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+ } else
+ cycx_x25_disconnect_response(card, 0, lcn);
+}
+
+/* LOG interrupt handler. */
+static void cycx_x25_irq_log(struct cycx_device *card, struct cycx_x25_cmd *cmd)
+{
+#if CYCLOMX_X25_DEBUG
+ char bf[20];
+ u16 size, toread, link, msg_code;
+ u8 code, routine;
+
+ cycx_peek(&card->hw, cmd->buf, &msg_code, sizeof(msg_code));
+ cycx_peek(&card->hw, cmd->buf + 2, &link, sizeof(link));
+ cycx_peek(&card->hw, cmd->buf + 4, &size, sizeof(size));
+ /* at most 20 bytes are available... thanks to Daniela :) */
+ toread = size < 20 ? size : 20;
+ cycx_peek(&card->hw, cmd->buf + 10, &bf, toread);
+ cycx_peek(&card->hw, cmd->buf + 10 + toread, &code, 1);
+ cycx_peek(&card->hw, cmd->buf + 10 + toread + 1, &routine, 1);
+
+ printk(KERN_INFO "cycx_x25_irq_handler: X25_LOG (0x4500) indic.:\n");
+ printk(KERN_INFO "cmd->buf=0x%X\n", cmd->buf);
+ printk(KERN_INFO "Log message code=0x%X\n", msg_code);
+ printk(KERN_INFO "Link=%d\n", link);
+ printk(KERN_INFO "log code=0x%X\n", code);
+ printk(KERN_INFO "log routine=0x%X\n", routine);
+ printk(KERN_INFO "Message size=%d\n", size);
+ hex_dump("Message", bf, toread);
+#endif
+}
+
+/* STATISTIC interrupt handler. */
+static void cycx_x25_irq_stat(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd)
+{
+ cycx_peek(&card->hw, cmd->buf, &card->u.x.stats,
+ sizeof(card->u.x.stats));
+ hex_dump("cycx_x25_irq_stat", (unsigned char*)&card->u.x.stats,
+ sizeof(card->u.x.stats));
+ cycx_x25_dump_stats(&card->u.x.stats);
+ wake_up_interruptible(&card->wait_stats);
+}
+
+/* Spurious interrupt handler.
+ * o print a warning
+ * If number of spurious interrupts exceeded some limit, then ??? */
+static void cycx_x25_irq_spurious(struct cycx_device *card,
+ struct cycx_x25_cmd *cmd)
+{
+ printk(KERN_INFO "%s: spurious interrupt (0x%X)!\n",
+ card->devname, cmd->command);
+}
+#ifdef CYCLOMX_X25_DEBUG
+static void hex_dump(char *msg, unsigned char *p, int len)
+{
+ unsigned char hex[1024],
+ * phex = hex;
+
+ if (len >= (sizeof(hex) / 2))
+ len = (sizeof(hex) / 2) - 1;
+
+ while (len--) {
+ sprintf(phex, "%02x", *p++);
+ phex += 2;
+ }
+
+ printk(KERN_INFO "%s: %s\n", msg, hex);
+}
+#endif
+
+/* Cyclom 2X Firmware-Specific Functions */
+/* Exec X.25 command. */
+static int x25_exec(struct cycx_device *card, int command, int link,
+ void *d1, int len1, void *d2, int len2)
+{
+ struct cycx_x25_cmd c;
+ unsigned long flags;
+ u32 addr = 0x1200 + 0x2E0 * link + 0x1E2;
+ u8 retry = CYCX_X25_MAX_CMD_RETRY;
+ int err = 0;
+
+ c.command = command;
+ c.link = link;
+ c.len = len1 + len2;
+
+ spin_lock_irqsave(&card->u.x.lock, flags);
+
+ /* write command */
+ cycx_poke(&card->hw, X25_MBOX_OFFS, &c, sizeof(c) - sizeof(c.buf));
+
+ /* write X.25 data */
+ if (d1) {
+ cycx_poke(&card->hw, addr, d1, len1);
+
+ if (d2) {
+ if (len2 > 254) {
+ u32 addr1 = 0xA00 + 0x400 * link;
+
+ cycx_poke(&card->hw, addr + len1, d2, 249);
+ cycx_poke(&card->hw, addr1, ((u8*)d2) + 249,
+ len2 - 249);
+ } else
+ cycx_poke(&card->hw, addr + len1, d2, len2);
+ }
+ }
+
+ /* generate interruption, executing command */
+ cycx_intr(&card->hw);
+
+ /* wait till card->mbox == 0 */
+ do {
+ err = cycx_exec(card->mbox);
+ } while (retry-- && err);
+
+ spin_unlock_irqrestore(&card->u.x.lock, flags);
+
+ return err;
+}
+
+/* Configure adapter. */
+static int cycx_x25_configure(struct cycx_device *card,
+ struct cycx_x25_config *conf)
+{
+ struct {
+ u16 nlinks;
+ struct cycx_x25_config conf[2];
+ } x25_cmd_conf;
+
+ memset(&x25_cmd_conf, 0, sizeof(x25_cmd_conf));
+ x25_cmd_conf.nlinks = 2;
+ x25_cmd_conf.conf[0] = *conf;
+ /* FIXME: we need to find a way in the wanrouter framework
+ to configure the second link, for now lets use it
+ with the same config from the first link, fixing
+ the interface type to RS232, the speed in 38400 and
+ the clock to external */
+ x25_cmd_conf.conf[1] = *conf;
+ x25_cmd_conf.conf[1].link = 1;
+ x25_cmd_conf.conf[1].speed = 5; /* 38400 */
+ x25_cmd_conf.conf[1].clock = 8;
+ x25_cmd_conf.conf[1].flags = 0; /* default = RS232 */
+
+ cycx_x25_dump_config(&x25_cmd_conf.conf[0]);
+ cycx_x25_dump_config(&x25_cmd_conf.conf[1]);
+
+ return x25_exec(card, X25_CONFIG, 0,
+ &x25_cmd_conf, sizeof(x25_cmd_conf), NULL, 0);
+}
+
+/* Get protocol statistics. */
+static int cycx_x25_get_stats(struct cycx_device *card)
+{
+ /* the firmware expects 20 in the size field!!!
+ thanks to Daniela */
+ int err = x25_exec(card, X25_STATISTIC, 0, NULL, 20, NULL, 0);
+
+ if (err)
+ return err;
+
+ interruptible_sleep_on(&card->wait_stats);
+
+ if (signal_pending(current))
+ return -EINTR;
+
+ card->wandev.stats.rx_packets = card->u.x.stats.n2_rx_frames;
+ card->wandev.stats.rx_over_errors = card->u.x.stats.rx_over_errors;
+ card->wandev.stats.rx_crc_errors = card->u.x.stats.rx_crc_errors;
+ card->wandev.stats.rx_length_errors = 0; /* not available from fw */
+ card->wandev.stats.rx_frame_errors = 0; /* not available from fw */
+ card->wandev.stats.rx_missed_errors = card->u.x.stats.rx_aborts;
+ card->wandev.stats.rx_dropped = 0; /* not available from fw */
+ card->wandev.stats.rx_errors = 0; /* not available from fw */
+ card->wandev.stats.tx_packets = card->u.x.stats.n2_tx_frames;
+ card->wandev.stats.tx_aborted_errors = card->u.x.stats.tx_aborts;
+ card->wandev.stats.tx_dropped = 0; /* not available from fw */
+ card->wandev.stats.collisions = 0; /* not available from fw */
+ card->wandev.stats.tx_errors = 0; /* not available from fw */
+
+ cycx_x25_dump_devs(&card->wandev);
+
+ return 0;
+}
+
+/* return the number of nibbles */
+static int byte_to_nibble(u8 *s, u8 *d, char *nibble)
+{
+ int i = 0;
+
+ if (*nibble && *s) {
+ d[i] |= *s++ - '0';
+ *nibble = 0;
+ ++i;
+ }
+
+ while (*s) {
+ d[i] = (*s - '0') << 4;
+ if (*(s + 1))
+ d[i] |= *(s + 1) - '0';
+ else {
+ *nibble = 1;
+ break;
+ }
+ ++i;
+ s += 2;
+ }
+
+ return i;
+}
+
+static void nibble_to_byte(u8 *s, u8 *d, u8 len, u8 nibble)
+{
+ if (nibble) {
+ *d++ = '0' + (*s++ & 0x0F);
+ --len;
+ }
+
+ while (len) {
+ *d++ = '0' + (*s >> 4);
+
+ if (--len) {
+ *d++ = '0' + (*s & 0x0F);
+ --len;
+ } else break;
+
+ ++s;
+ }
+
+ *d = '\0';
+}
+
+/* Place X.25 call. */
+static int x25_place_call(struct cycx_device *card,
+ struct cycx_x25_channel *chan)
+{
+ int err = 0,
+ len;
+ char d[64],
+ nibble = 0,
+ mylen = chan->local_addr ? strlen(chan->local_addr) : 0,
+ remotelen = strlen(chan->addr);
+ u8 key;
+
+ if (card->u.x.connection_keys == ~0U) {
+ printk(KERN_INFO "%s: too many simultaneous connection "
+ "requests!\n", card->devname);
+ return -EAGAIN;
+ }
+
+ key = ffz(card->u.x.connection_keys);
+ set_bit(key, (void*)&card->u.x.connection_keys);
+ ++key;
+ dprintk(1, KERN_INFO "%s:x25_place_call:key=%d\n", card->devname, key);
+ memset(d, 0, sizeof(d));
+ d[1] = key; /* user key */
+ d[2] = 0x10;
+ d[4] = 0x0B;
+
+ len = byte_to_nibble(chan->addr, d + 6, &nibble);
+
+ if (chan->local_addr)
+ len += byte_to_nibble(chan->local_addr, d + 6 + len, &nibble);
+
+ if (nibble)
+ ++len;
+
+ d[5] = mylen << 4 | remotelen;
+ d[6 + len + 1] = 0xCC; /* TCP/IP over X.25, thanks to Daniela :) */
+
+ if ((err = x25_exec(card, X25_CONNECT_REQUEST, chan->link,
+ &d, 7 + len + 1, NULL, 0)) != 0)
+ clear_bit(--key, (void*)&card->u.x.connection_keys);
+ else
+ chan->lcn = -key;
+
+ return err;
+}
+
+/* Place X.25 CONNECT RESPONSE. */
+static int cycx_x25_connect_response(struct cycx_device *card,
+ struct cycx_x25_channel *chan)
+{
+ u8 d[8];
+
+ memset(d, 0, sizeof(d));
+ d[0] = d[3] = chan->lcn;
+ d[2] = 0x10;
+ d[4] = 0x0F;
+ d[7] = 0xCC; /* TCP/IP over X.25, thanks Daniela */
+
+ return x25_exec(card, X25_CONNECT_RESPONSE, chan->link, &d, 8, NULL, 0);
+}
+
+/* Place X.25 DISCONNECT RESPONSE. */
+static int cycx_x25_disconnect_response(struct cycx_device *card, u8 link,
+ u8 lcn)
+{
+ char d[5];
+
+ memset(d, 0, sizeof(d));
+ d[0] = d[3] = lcn;
+ d[2] = 0x10;
+ d[4] = 0x17;
+
+ return x25_exec(card, X25_DISCONNECT_RESPONSE, link, &d, 5, NULL, 0);
+}
+
+/* Clear X.25 call. */
+static int x25_clear_call(struct cycx_device *card, u8 link, u8 lcn, u8 cause,
+ u8 diagn)
+{
+ u8 d[7];
+
+ memset(d, 0, sizeof(d));
+ d[0] = d[3] = lcn;
+ d[2] = 0x10;
+ d[4] = 0x13;
+ d[5] = cause;
+ d[6] = diagn;
+
+ return x25_exec(card, X25_DISCONNECT_REQUEST, link, d, 7, NULL, 0);
+}
+
+/* Send X.25 data packet. */
+static int cycx_x25_send(struct cycx_device *card, u8 link, u8 lcn, u8 bitm,
+ int len, void *buf)
+{
+ u8 d[] = "?\xFF\x10??";
+
+ d[0] = d[3] = lcn;
+ d[4] = bitm;
+
+ return x25_exec(card, X25_DATA_REQUEST, link, &d, 5, buf, len);
+}
+
+/* Miscellaneous */
+/* Find network device by its channel number. */
+static struct net_device *cycx_x25_get_dev_by_lcn(struct wan_device *wandev,
+ s16 lcn)
+{
+ struct net_device *dev = wandev->dev;
+ struct cycx_x25_channel *chan;
+
+ while (dev) {
+ chan = (struct cycx_x25_channel*)dev->priv;
+
+ if (chan->lcn == lcn)
+ break;
+ dev = chan->slave;
+ }
+ return dev;
+}
+
+/* Find network device by its remote dte address. */
+static struct net_device *
+ cycx_x25_get_dev_by_dte_addr(struct wan_device *wandev, char *dte)
+{
+ struct net_device *dev = wandev->dev;
+ struct cycx_x25_channel *chan;
+
+ while (dev) {
+ chan = (struct cycx_x25_channel*)dev->priv;
+
+ if (!strcmp(chan->addr, dte))
+ break;
+ dev = chan->slave;
+ }
+ return dev;
+}
+
+/* Initiate connection on the logical channel.
+ * o for PVC we just get channel configuration
+ * o for SVCs place an X.25 call
+ *
+ * Return: 0 connected
+ * >0 connection in progress
+ * <0 failure */
+static int cycx_x25_chan_connect(struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+ struct cycx_device *card = chan->card;
+
+ if (chan->svc) {
+ if (!chan->addr[0])
+ return -EINVAL; /* no destination address */
+
+ dprintk(1, KERN_INFO "%s: placing X.25 call to %s...\n",
+ card->devname, chan->addr);
+
+ if (x25_place_call(card, chan))
+ return -EIO;
+
+ cycx_x25_set_chan_state(dev, WAN_CONNECTING);
+ return 1;
+ } else
+ cycx_x25_set_chan_state(dev, WAN_CONNECTED);
+
+ return 0;
+}
+
+/* Disconnect logical channel.
+ * o if SVC then clear X.25 call */
+static void cycx_x25_chan_disconnect(struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+
+ if (chan->svc) {
+ x25_clear_call(chan->card, chan->link, chan->lcn, 0, 0);
+ cycx_x25_set_chan_state(dev, WAN_DISCONNECTING);
+ } else
+ cycx_x25_set_chan_state(dev, WAN_DISCONNECTED);
+}
+
+/* Called by kernel timer */
+static void cycx_x25_chan_timer(unsigned long d)
+{
+ struct net_device *dev = (struct net_device *)d;
+ struct cycx_x25_channel *chan = dev->priv;
+
+ if (chan->state == WAN_CONNECTED)
+ cycx_x25_chan_disconnect(dev);
+ else
+ printk(KERN_ERR "%s: %s for svc (%s) not connected!\n",
+ chan->card->devname, __FUNCTION__, dev->name);
+}
+
+/* Set logical channel state. */
+static void cycx_x25_set_chan_state(struct net_device *dev, u8 state)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+ struct cycx_device *card = chan->card;
+ unsigned long flags;
+ char *string_state = NULL;
+
+ spin_lock_irqsave(&card->lock, flags);
+
+ if (chan->state != state) {
+ if (chan->svc && chan->state == WAN_CONNECTED)
+ del_timer(&chan->timer);
+
+ switch (state) {
+ case WAN_CONNECTED:
+ string_state = "connected!";
+ *(u16*)dev->dev_addr = htons(chan->lcn);
+ netif_wake_queue(dev);
+ reset_timer(dev);
+
+ if (chan->protocol == ETH_P_X25)
+ cycx_x25_chan_send_event(dev, 1);
+
+ break;
+ case WAN_CONNECTING:
+ string_state = "connecting...";
+ break;
+ case WAN_DISCONNECTING:
+ string_state = "disconnecting...";
+ break;
+ case WAN_DISCONNECTED:
+ string_state = "disconnected!";
+
+ if (chan->svc) {
+ *(unsigned short*)dev->dev_addr = 0;
+ chan->lcn = 0;
+ }
+
+ if (chan->protocol == ETH_P_X25)
+ cycx_x25_chan_send_event(dev, 2);
+
+ netif_wake_queue(dev);
+ break;
+ }
+
+ printk(KERN_INFO "%s: interface %s %s\n", card->devname,
+ dev->name, string_state);
+ chan->state = state;
+ }
+
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+/* Send packet on a logical channel.
+ * When this function is called, tx_skb field of the channel data space
+ * points to the transmit socket buffer. When transmission is complete,
+ * release socket buffer and reset 'tbusy' flag.
+ *
+ * Return: 0 - transmission complete
+ * 1 - busy
+ *
+ * Notes:
+ * 1. If packet length is greater than MTU for this channel, we'll fragment
+ * the packet into 'complete sequence' using M-bit.
+ * 2. When transmission is complete, an event notification should be issued
+ * to the router. */
+static int cycx_x25_chan_send(struct net_device *dev, struct sk_buff *skb)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+ struct cycx_device *card = chan->card;
+ int bitm = 0; /* final packet */
+ unsigned len = skb->len;
+
+ if (skb->len > card->wandev.mtu) {
+ len = card->wandev.mtu;
+ bitm = 0x10; /* set M-bit (more data) */
+ }
+
+ if (cycx_x25_send(card, chan->link, chan->lcn, bitm, len, skb->data))
+ return 1;
+
+ if (bitm) {
+ skb_pull(skb, len);
+ return 1;
+ }
+
+ ++chan->ifstats.tx_packets;
+ chan->ifstats.tx_bytes += len;
+
+ return 0;
+}
+
+/* Send event (connection, disconnection, etc) to X.25 socket layer */
+
+static void cycx_x25_chan_send_event(struct net_device *dev, u8 event)
+{
+ struct sk_buff *skb;
+ unsigned char *ptr;
+
+ if ((skb = dev_alloc_skb(1)) == NULL) {
+ printk(KERN_ERR "%s: out of memory\n", __FUNCTION__);
+ return;
+ }
+
+ ptr = skb_put(skb, 1);
+ *ptr = event;
+
+ skb->protocol = x25_type_trans(skb, dev);
+ netif_rx(skb);
+ dev->last_rx = jiffies; /* timestamp */
+}
+
+/* Convert line speed in bps to a number used by cyclom 2x code. */
+static u8 bps_to_speed_code(u32 bps)
+{
+ u8 number = 0; /* defaults to the lowest (1200) speed ;> */
+
+ if (bps >= 512000) number = 8;
+ else if (bps >= 256000) number = 7;
+ else if (bps >= 64000) number = 6;
+ else if (bps >= 38400) number = 5;
+ else if (bps >= 19200) number = 4;
+ else if (bps >= 9600) number = 3;
+ else if (bps >= 4800) number = 2;
+ else if (bps >= 2400) number = 1;
+
+ return number;
+}
+
+/* log base 2 */
+static u8 cycx_log2(u32 n)
+{
+ u8 log = 0;
+
+ if (!n)
+ return 0;
+
+ while (n > 1) {
+ n >>= 1;
+ ++log;
+ }
+
+ return log;
+}
+
+/* Convert decimal string to unsigned integer.
+ * If len != 0 then only 'len' characters of the string are converted. */
+static unsigned dec_to_uint(u8 *str, int len)
+{
+ unsigned val = 0;
+
+ if (!len)
+ len = strlen(str);
+
+ for (; len && is_digit(*str); ++str, --len)
+ val = (val * 10) + (*str - (unsigned) '0');
+
+ return val;
+}
+
+static void reset_timer(struct net_device *dev)
+{
+ struct cycx_x25_channel *chan = dev->priv;
+
+ if (chan->svc)
+ mod_timer(&chan->timer, jiffies+chan->idle_tmout*HZ);
+}
+#ifdef CYCLOMX_X25_DEBUG
+static void cycx_x25_dump_config(struct cycx_x25_config *conf)
+{
+ printk(KERN_INFO "X.25 configuration\n");
+ printk(KERN_INFO "-----------------\n");
+ printk(KERN_INFO "link number=%d\n", conf->link);
+ printk(KERN_INFO "line speed=%d\n", conf->speed);
+ printk(KERN_INFO "clock=%sternal\n", conf->clock == 8 ? "Ex" : "In");
+ printk(KERN_INFO "# level 2 retransm.=%d\n", conf->n2);
+ printk(KERN_INFO "level 2 window=%d\n", conf->n2win);
+ printk(KERN_INFO "level 3 window=%d\n", conf->n3win);
+ printk(KERN_INFO "# logical channels=%d\n", conf->nvc);
+ printk(KERN_INFO "level 3 pkt len=%d\n", conf->pktlen);
+ printk(KERN_INFO "my address=%d\n", conf->locaddr);
+ printk(KERN_INFO "remote address=%d\n", conf->remaddr);
+ printk(KERN_INFO "t1=%d seconds\n", conf->t1);
+ printk(KERN_INFO "t2=%d seconds\n", conf->t2);
+ printk(KERN_INFO "t21=%d seconds\n", conf->t21);
+ printk(KERN_INFO "# PVCs=%d\n", conf->npvc);
+ printk(KERN_INFO "t23=%d seconds\n", conf->t23);
+ printk(KERN_INFO "flags=0x%x\n", conf->flags);
+}
+
+static void cycx_x25_dump_stats(struct cycx_x25_stats *stats)
+{
+ printk(KERN_INFO "X.25 statistics\n");
+ printk(KERN_INFO "--------------\n");
+ printk(KERN_INFO "rx_crc_errors=%d\n", stats->rx_crc_errors);
+ printk(KERN_INFO "rx_over_errors=%d\n", stats->rx_over_errors);
+ printk(KERN_INFO "n2_tx_frames=%d\n", stats->n2_tx_frames);
+ printk(KERN_INFO "n2_rx_frames=%d\n", stats->n2_rx_frames);
+ printk(KERN_INFO "tx_timeouts=%d\n", stats->tx_timeouts);
+ printk(KERN_INFO "rx_timeouts=%d\n", stats->rx_timeouts);
+ printk(KERN_INFO "n3_tx_packets=%d\n", stats->n3_tx_packets);
+ printk(KERN_INFO "n3_rx_packets=%d\n", stats->n3_rx_packets);
+ printk(KERN_INFO "tx_aborts=%d\n", stats->tx_aborts);
+ printk(KERN_INFO "rx_aborts=%d\n", stats->rx_aborts);
+}
+
+static void cycx_x25_dump_devs(struct wan_device *wandev)
+{
+ struct net_device *dev = wandev->dev;
+
+ printk(KERN_INFO "X.25 dev states\n");
+ printk(KERN_INFO "name: addr: txoff: protocol:\n");
+ printk(KERN_INFO "---------------------------------------\n");
+
+ while(dev) {
+ struct cycx_x25_channel *chan = dev->priv;
+
+ printk(KERN_INFO "%-5.5s %-15.15s %d ETH_P_%s\n",
+ chan->name, chan->addr, netif_queue_stopped(dev),
+ chan->protocol == ETH_P_IP ? "IP" : "X25");
+ dev = chan->slave;
+ }
+}
+
+#endif /* CYCLOMX_X25_DEBUG */
+/* End */
diff --git a/drivers/net/wan/dlci.c b/drivers/net/wan/dlci.c
new file mode 100644
index 00000000000..6e1ec5bf22f
--- /dev/null
+++ b/drivers/net/wan/dlci.c
@@ -0,0 +1,566 @@
+/*
+ * DLCI Implementation of Frame Relay protocol for Linux, according to
+ * RFC 1490. This generic device provides en/decapsulation for an
+ * underlying hardware driver. Routes & IPs are assigned to these
+ * interfaces. Requires 'dlcicfg' program to create usable
+ * interfaces, the initial one, 'dlci' is for IOCTL use only.
+ *
+ * Version: @(#)dlci.c 0.35 4 Jan 1997
+ *
+ * Author: Mike McLagan <mike.mclagan@linux.org>
+ *
+ * Changes:
+ *
+ * 0.15 Mike Mclagan Packet freeing, bug in kmalloc call
+ * DLCI_RET handling
+ * 0.20 Mike McLagan More conservative on which packets
+ * are returned for retry and which are
+ * are dropped. If DLCI_RET_DROP is
+ * returned from the FRAD, the packet is
+ * sent back to Linux for re-transmission
+ * 0.25 Mike McLagan Converted to use SIOC IOCTL calls
+ * 0.30 Jim Freeman Fixed to allow IPX traffic
+ * 0.35 Michael Elizabeth Fixed incorrect memcpy_fromfs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h> /* for CONFIG_DLCI_COUNT */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/if_frad.h>
+#include <linux/bitops.h>
+
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+
+static const char version[] = "DLCI driver v0.35, 4 Jan 1997, mike.mclagan@linux.org";
+
+static LIST_HEAD(dlci_devs);
+
+static void dlci_setup(struct net_device *);
+
+/*
+ * these encapsulate the RFC 1490 requirements as well as
+ * deal with packet transmission and reception, working with
+ * the upper network layers
+ */
+
+static int dlci_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr,
+ unsigned len)
+{
+ struct frhdr hdr;
+ struct dlci_local *dlp;
+ unsigned int hlen;
+ char *dest;
+
+ dlp = dev->priv;
+
+ hdr.control = FRAD_I_UI;
+ switch(type)
+ {
+ case ETH_P_IP:
+ hdr.IP_NLPID = FRAD_P_IP;
+ hlen = sizeof(hdr.control) + sizeof(hdr.IP_NLPID);
+ break;
+
+ /* feel free to add other types, if necessary */
+
+ default:
+ hdr.pad = FRAD_P_PADDING;
+ hdr.NLPID = FRAD_P_SNAP;
+ memset(hdr.OUI, 0, sizeof(hdr.OUI));
+ hdr.PID = htons(type);
+ hlen = sizeof(hdr);
+ break;
+ }
+
+ dest = skb_push(skb, hlen);
+ if (!dest)
+ return(0);
+
+ memcpy(dest, &hdr, hlen);
+
+ return(hlen);
+}
+
+static void dlci_receive(struct sk_buff *skb, struct net_device *dev)
+{
+ struct dlci_local *dlp;
+ struct frhdr *hdr;
+ int process, header;
+
+ dlp = dev->priv;
+ if (!pskb_may_pull(skb, sizeof(*hdr))) {
+ printk(KERN_NOTICE "%s: invalid data no header\n",
+ dev->name);
+ dlp->stats.rx_errors++;
+ kfree_skb(skb);
+ return;
+ }
+
+ hdr = (struct frhdr *) skb->data;
+ process = 0;
+ header = 0;
+ skb->dev = dev;
+
+ if (hdr->control != FRAD_I_UI)
+ {
+ printk(KERN_NOTICE "%s: Invalid header flag 0x%02X.\n", dev->name, hdr->control);
+ dlp->stats.rx_errors++;
+ }
+ else
+ switch(hdr->IP_NLPID)
+ {
+ case FRAD_P_PADDING:
+ if (hdr->NLPID != FRAD_P_SNAP)
+ {
+ printk(KERN_NOTICE "%s: Unsupported NLPID 0x%02X.\n", dev->name, hdr->NLPID);
+ dlp->stats.rx_errors++;
+ break;
+ }
+
+ if (hdr->OUI[0] + hdr->OUI[1] + hdr->OUI[2] != 0)
+ {
+ printk(KERN_NOTICE "%s: Unsupported organizationally unique identifier 0x%02X-%02X-%02X.\n", dev->name, hdr->OUI[0], hdr->OUI[1], hdr->OUI[2]);
+ dlp->stats.rx_errors++;
+ break;
+ }
+
+ /* at this point, it's an EtherType frame */
+ header = sizeof(struct frhdr);
+ /* Already in network order ! */
+ skb->protocol = hdr->PID;
+ process = 1;
+ break;
+
+ case FRAD_P_IP:
+ header = sizeof(hdr->control) + sizeof(hdr->IP_NLPID);
+ skb->protocol = htons(ETH_P_IP);
+ process = 1;
+ break;
+
+ case FRAD_P_SNAP:
+ case FRAD_P_Q933:
+ case FRAD_P_CLNP:
+ printk(KERN_NOTICE "%s: Unsupported NLPID 0x%02X.\n", dev->name, hdr->pad);
+ dlp->stats.rx_errors++;
+ break;
+
+ default:
+ printk(KERN_NOTICE "%s: Invalid pad byte 0x%02X.\n", dev->name, hdr->pad);
+ dlp->stats.rx_errors++;
+ break;
+ }
+
+ if (process)
+ {
+ /* we've set up the protocol, so discard the header */
+ skb->mac.raw = skb->data;
+ skb_pull(skb, header);
+ dlp->stats.rx_bytes += skb->len;
+ netif_rx(skb);
+ dlp->stats.rx_packets++;
+ dev->last_rx = jiffies;
+ }
+ else
+ dev_kfree_skb(skb);
+}
+
+static int dlci_transmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct dlci_local *dlp;
+ int ret;
+
+ ret = 0;
+
+ if (!skb || !dev)
+ return(0);
+
+ dlp = dev->priv;
+
+ netif_stop_queue(dev);
+
+ ret = dlp->slave->hard_start_xmit(skb, dlp->slave);
+ switch (ret)
+ {
+ case DLCI_RET_OK:
+ dlp->stats.tx_packets++;
+ ret = 0;
+ break;
+ case DLCI_RET_ERR:
+ dlp->stats.tx_errors++;
+ ret = 0;
+ break;
+ case DLCI_RET_DROP:
+ dlp->stats.tx_dropped++;
+ ret = 1;
+ break;
+ }
+ /* Alan Cox recommends always returning 0, and always freeing the packet */
+ /* experience suggest a slightly more conservative approach */
+
+ if (!ret)
+ {
+ dev_kfree_skb(skb);
+ netif_wake_queue(dev);
+ }
+ return(ret);
+}
+
+static int dlci_config(struct net_device *dev, struct dlci_conf __user *conf, int get)
+{
+ struct dlci_conf config;
+ struct dlci_local *dlp;
+ struct frad_local *flp;
+ int err;
+
+ dlp = dev->priv;
+
+ flp = dlp->slave->priv;
+
+ if (!get)
+ {
+ if(copy_from_user(&config, conf, sizeof(struct dlci_conf)))
+ return -EFAULT;
+ if (config.flags & ~DLCI_VALID_FLAGS)
+ return(-EINVAL);
+ memcpy(&dlp->config, &config, sizeof(struct dlci_conf));
+ dlp->configured = 1;
+ }
+
+ err = (*flp->dlci_conf)(dlp->slave, dev, get);
+ if (err)
+ return(err);
+
+ if (get)
+ {
+ if(copy_to_user(conf, &dlp->config, sizeof(struct dlci_conf)))
+ return -EFAULT;
+ }
+
+ return(0);
+}
+
+static int dlci_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct dlci_local *dlp;
+
+ if (!capable(CAP_NET_ADMIN))
+ return(-EPERM);
+
+ dlp = dev->priv;
+
+ switch(cmd)
+ {
+ case DLCI_GET_SLAVE:
+ if (!*(short *)(dev->dev_addr))
+ return(-EINVAL);
+
+ strncpy(ifr->ifr_slave, dlp->slave->name, sizeof(ifr->ifr_slave));
+ break;
+
+ case DLCI_GET_CONF:
+ case DLCI_SET_CONF:
+ if (!*(short *)(dev->dev_addr))
+ return(-EINVAL);
+
+ return(dlci_config(dev, ifr->ifr_data, cmd == DLCI_GET_CONF));
+ break;
+
+ default:
+ return(-EOPNOTSUPP);
+ }
+ return(0);
+}
+
+static int dlci_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct dlci_local *dlp;
+
+ dlp = dev->priv;
+
+ return((*dlp->slave->change_mtu)(dlp->slave, new_mtu));
+}
+
+static int dlci_open(struct net_device *dev)
+{
+ struct dlci_local *dlp;
+ struct frad_local *flp;
+ int err;
+
+ dlp = dev->priv;
+
+ if (!*(short *)(dev->dev_addr))
+ return(-EINVAL);
+
+ if (!netif_running(dlp->slave))
+ return(-ENOTCONN);
+
+ flp = dlp->slave->priv;
+ err = (*flp->activate)(dlp->slave, dev);
+ if (err)
+ return(err);
+
+ netif_start_queue(dev);
+
+ return 0;
+}
+
+static int dlci_close(struct net_device *dev)
+{
+ struct dlci_local *dlp;
+ struct frad_local *flp;
+ int err;
+
+ netif_stop_queue(dev);
+
+ dlp = dev->priv;
+
+ flp = dlp->slave->priv;
+ err = (*flp->deactivate)(dlp->slave, dev);
+
+ return 0;
+}
+
+static struct net_device_stats *dlci_get_stats(struct net_device *dev)
+{
+ struct dlci_local *dlp;
+
+ dlp = dev->priv;
+
+ return(&dlp->stats);
+}
+
+static int dlci_add(struct dlci_add *dlci)
+{
+ struct net_device *master, *slave;
+ struct dlci_local *dlp;
+ struct frad_local *flp;
+ int err = -EINVAL;
+
+
+ /* validate slave device */
+ slave = dev_get_by_name(dlci->devname);
+ if (!slave)
+ return -ENODEV;
+
+ if (slave->type != ARPHRD_FRAD || slave->priv == NULL)
+ goto err1;
+
+ /* create device name */
+ master = alloc_netdev( sizeof(struct dlci_local), "dlci%d",
+ dlci_setup);
+ if (!master) {
+ err = -ENOMEM;
+ goto err1;
+ }
+
+ /* make sure same slave not already registered */
+ rtnl_lock();
+ list_for_each_entry(dlp, &dlci_devs, list) {
+ if (dlp->slave == slave) {
+ err = -EBUSY;
+ goto err2;
+ }
+ }
+
+ err = dev_alloc_name(master, master->name);
+ if (err < 0)
+ goto err2;
+
+ *(short *)(master->dev_addr) = dlci->dlci;
+
+ dlp = (struct dlci_local *) master->priv;
+ dlp->slave = slave;
+ dlp->master = master;
+
+ flp = slave->priv;
+ err = (*flp->assoc)(slave, master);
+ if (err < 0)
+ goto err2;
+
+ err = register_netdevice(master);
+ if (err < 0)
+ goto err2;
+
+ strcpy(dlci->devname, master->name);
+
+ list_add(&dlp->list, &dlci_devs);
+ rtnl_unlock();
+
+ return(0);
+
+ err2:
+ rtnl_unlock();
+ free_netdev(master);
+ err1:
+ dev_put(slave);
+ return(err);
+}
+
+static int dlci_del(struct dlci_add *dlci)
+{
+ struct dlci_local *dlp;
+ struct frad_local *flp;
+ struct net_device *master, *slave;
+ int err;
+
+ /* validate slave device */
+ master = __dev_get_by_name(dlci->devname);
+ if (!master)
+ return(-ENODEV);
+
+ if (netif_running(master)) {
+ return(-EBUSY);
+ }
+
+ dlp = master->priv;
+ slave = dlp->slave;
+ flp = slave->priv;
+
+ rtnl_lock();
+ err = (*flp->deassoc)(slave, master);
+ if (!err) {
+ list_del(&dlp->list);
+
+ unregister_netdevice(master);
+
+ dev_put(slave);
+ }
+ rtnl_unlock();
+
+ return(err);
+}
+
+static int dlci_ioctl(unsigned int cmd, void __user *arg)
+{
+ struct dlci_add add;
+ int err;
+
+ if (!capable(CAP_NET_ADMIN))
+ return(-EPERM);
+
+ if(copy_from_user(&add, arg, sizeof(struct dlci_add)))
+ return -EFAULT;
+
+ switch (cmd)
+ {
+ case SIOCADDDLCI:
+ err = dlci_add(&add);
+
+ if (!err)
+ if(copy_to_user(arg, &add, sizeof(struct dlci_add)))
+ return -EFAULT;
+ break;
+
+ case SIOCDELDLCI:
+ err = dlci_del(&add);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ return(err);
+}
+
+static void dlci_setup(struct net_device *dev)
+{
+ struct dlci_local *dlp = dev->priv;
+
+ dev->flags = 0;
+ dev->open = dlci_open;
+ dev->stop = dlci_close;
+ dev->do_ioctl = dlci_dev_ioctl;
+ dev->hard_start_xmit = dlci_transmit;
+ dev->hard_header = dlci_header;
+ dev->get_stats = dlci_get_stats;
+ dev->change_mtu = dlci_change_mtu;
+ dev->destructor = free_netdev;
+
+ dlp->receive = dlci_receive;
+
+ dev->type = ARPHRD_DLCI;
+ dev->hard_header_len = sizeof(struct frhdr);
+ dev->addr_len = sizeof(short);
+
+}
+
+/* if slave is unregistering, then cleanup master */
+static int dlci_dev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *) ptr;
+
+ if (event == NETDEV_UNREGISTER) {
+ struct dlci_local *dlp;
+
+ list_for_each_entry(dlp, &dlci_devs, list) {
+ if (dlp->slave == dev) {
+ list_del(&dlp->list);
+ unregister_netdevice(dlp->master);
+ dev_put(dlp->slave);
+ break;
+ }
+ }
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block dlci_notifier = {
+ .notifier_call = dlci_dev_event,
+};
+
+static int __init init_dlci(void)
+{
+ dlci_ioctl_set(dlci_ioctl);
+ register_netdevice_notifier(&dlci_notifier);
+
+ printk("%s.\n", version);
+
+ return 0;
+}
+
+static void __exit dlci_exit(void)
+{
+ struct dlci_local *dlp, *nxt;
+
+ dlci_ioctl_set(NULL);
+ unregister_netdevice_notifier(&dlci_notifier);
+
+ rtnl_lock();
+ list_for_each_entry_safe(dlp, nxt, &dlci_devs, list) {
+ unregister_netdevice(dlp->master);
+ dev_put(dlp->slave);
+ }
+ rtnl_unlock();
+}
+
+module_init(init_dlci);
+module_exit(dlci_exit);
+
+MODULE_AUTHOR("Mike McLagan");
+MODULE_DESCRIPTION("Frame Relay DLCI layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/dscc4.c b/drivers/net/wan/dscc4.c
new file mode 100644
index 00000000000..520a77a798e
--- /dev/null
+++ b/drivers/net/wan/dscc4.c
@@ -0,0 +1,2074 @@
+/*
+ * drivers/net/wan/dscc4/dscc4.c: a DSCC4 HDLC driver for Linux
+ *
+ * This software may be used and distributed according to the terms of the
+ * GNU General Public License.
+ *
+ * The author may be reached as romieu@cogenit.fr.
+ * Specific bug reports/asian food will be welcome.
+ *
+ * Special thanks to the nice people at CS-Telecom for the hardware and the
+ * access to the test/measure tools.
+ *
+ *
+ * Theory of Operation
+ *
+ * I. Board Compatibility
+ *
+ * This device driver is designed for the Siemens PEB20534 4 ports serial
+ * controller as found on Etinc PCISYNC cards. The documentation for the
+ * chipset is available at http://www.infineon.com:
+ * - Data Sheet "DSCC4, DMA Supported Serial Communication Controller with
+ * 4 Channels, PEB 20534 Version 2.1, PEF 20534 Version 2.1";
+ * - Application Hint "Management of DSCC4 on-chip FIFO resources".
+ * - Errata sheet DS5 (courtesy of Michael Skerritt).
+ * Jens David has built an adapter based on the same chipset. Take a look
+ * at http://www.afthd.tu-darmstadt.de/~dg1kjd/pciscc4 for a specific
+ * driver.
+ * Sample code (2 revisions) is available at Infineon.
+ *
+ * II. Board-specific settings
+ *
+ * Pcisync can transmit some clock signal to the outside world on the
+ * *first two* ports provided you put a quartz and a line driver on it and
+ * remove the jumpers. The operation is described on Etinc web site. If you
+ * go DCE on these ports, don't forget to use an adequate cable.
+ *
+ * Sharing of the PCI interrupt line for this board is possible.
+ *
+ * III. Driver operation
+ *
+ * The rx/tx operations are based on a linked list of descriptors. The driver
+ * doesn't use HOLD mode any more. HOLD mode is definitely buggy and the more
+ * I tried to fix it, the more it started to look like (convoluted) software
+ * mutation of LxDA method. Errata sheet DS5 suggests to use LxDA: consider
+ * this a rfc2119 MUST.
+ *
+ * Tx direction
+ * When the tx ring is full, the xmit routine issues a call to netdev_stop.
+ * The device is supposed to be enabled again during an ALLS irq (we could
+ * use HI but as it's easy to lose events, it's fscked).
+ *
+ * Rx direction
+ * The received frames aren't supposed to span over multiple receiving areas.
+ * I may implement it some day but it isn't the highest ranked item.
+ *
+ * IV. Notes
+ * The current error (XDU, RFO) recovery code is untested.
+ * So far, RDO takes his RX channel down and the right sequence to enable it
+ * again is still a mistery. If RDO happens, plan a reboot. More details
+ * in the code (NB: as this happens, TX still works).
+ * Don't mess the cables during operation, especially on DTE ports. I don't
+ * suggest it for DCE either but at least one can get some messages instead
+ * of a complete instant freeze.
+ * Tests are done on Rev. 20 of the silicium. The RDO handling changes with
+ * the documentation/chipset releases.
+ *
+ * TODO:
+ * - test X25.
+ * - use polling at high irq/s,
+ * - performance analysis,
+ * - endianness.
+ *
+ * 2001/12/10 Daniela Squassoni <daniela@cyclades.com>
+ * - Contribution to support the new generic HDLC layer.
+ *
+ * 2002/01 Ueimor
+ * - old style interface removal
+ * - dscc4_release_ring fix (related to DMA mapping)
+ * - hard_start_xmit fix (hint: TxSizeMax)
+ * - misc crapectomy.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+
+#include <asm/system.h>
+#include <asm/cache.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <linux/init.h>
+#include <linux/string.h>
+
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <net/syncppp.h>
+#include <linux/hdlc.h>
+
+/* Version */
+static const char version[] = "$Id: dscc4.c,v 1.173 2003/09/20 23:55:34 romieu Exp $ for Linux\n";
+static int debug;
+static int quartz;
+
+#ifdef CONFIG_DSCC4_PCI_RST
+static DECLARE_MUTEX(dscc4_sem);
+static u32 dscc4_pci_config_store[16];
+#endif
+
+#define DRV_NAME "dscc4"
+
+#undef DSCC4_POLLING
+
+/* Module parameters */
+
+MODULE_AUTHOR("Maintainer: Francois Romieu <romieu@cogenit.fr>");
+MODULE_DESCRIPTION("Siemens PEB20534 PCI Controler");
+MODULE_LICENSE("GPL");
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug,"Enable/disable extra messages");
+module_param(quartz, int, 0);
+MODULE_PARM_DESC(quartz,"If present, on-board quartz frequency (Hz)");
+
+/* Structures */
+
+struct thingie {
+ int define;
+ u32 bits;
+};
+
+struct TxFD {
+ u32 state;
+ u32 next;
+ u32 data;
+ u32 complete;
+ u32 jiffies; /* Allows sizeof(TxFD) == sizeof(RxFD) + extra hack */
+};
+
+struct RxFD {
+ u32 state1;
+ u32 next;
+ u32 data;
+ u32 state2;
+ u32 end;
+};
+
+#define DUMMY_SKB_SIZE 64
+#define TX_LOW 8
+#define TX_RING_SIZE 32
+#define RX_RING_SIZE 32
+#define TX_TOTAL_SIZE TX_RING_SIZE*sizeof(struct TxFD)
+#define RX_TOTAL_SIZE RX_RING_SIZE*sizeof(struct RxFD)
+#define IRQ_RING_SIZE 64 /* Keep it a multiple of 32 */
+#define TX_TIMEOUT (HZ/10)
+#define DSCC4_HZ_MAX 33000000
+#define BRR_DIVIDER_MAX 64*0x00004000 /* Cf errata DS5 p.10 */
+#define dev_per_card 4
+#define SCC_REGISTERS_MAX 23 /* Cf errata DS5 p.4 */
+
+#define SOURCE_ID(flags) (((flags) >> 28) & 0x03)
+#define TO_SIZE(state) (((state) >> 16) & 0x1fff)
+
+/*
+ * Given the operating range of Linux HDLC, the 2 defines below could be
+ * made simpler. However they are a fine reminder for the limitations of
+ * the driver: it's better to stay < TxSizeMax and < RxSizeMax.
+ */
+#define TO_STATE_TX(len) cpu_to_le32(((len) & TxSizeMax) << 16)
+#define TO_STATE_RX(len) cpu_to_le32((RX_MAX(len) % RxSizeMax) << 16)
+#define RX_MAX(len) ((((len) >> 5) + 1) << 5) /* Cf RLCR */
+#define SCC_REG_START(dpriv) (SCC_START+(dpriv->dev_id)*SCC_OFFSET)
+
+struct dscc4_pci_priv {
+ u32 *iqcfg;
+ int cfg_cur;
+ spinlock_t lock;
+ struct pci_dev *pdev;
+
+ struct dscc4_dev_priv *root;
+ dma_addr_t iqcfg_dma;
+ u32 xtal_hz;
+};
+
+struct dscc4_dev_priv {
+ struct sk_buff *rx_skbuff[RX_RING_SIZE];
+ struct sk_buff *tx_skbuff[TX_RING_SIZE];
+
+ struct RxFD *rx_fd;
+ struct TxFD *tx_fd;
+ u32 *iqrx;
+ u32 *iqtx;
+
+ /* FIXME: check all the volatile are required */
+ volatile u32 tx_current;
+ u32 rx_current;
+ u32 iqtx_current;
+ u32 iqrx_current;
+
+ volatile u32 tx_dirty;
+ volatile u32 ltda;
+ u32 rx_dirty;
+ u32 lrda;
+
+ dma_addr_t tx_fd_dma;
+ dma_addr_t rx_fd_dma;
+ dma_addr_t iqtx_dma;
+ dma_addr_t iqrx_dma;
+
+ u32 scc_regs[SCC_REGISTERS_MAX]; /* Cf errata DS5 p.4 */
+
+ struct timer_list timer;
+
+ struct dscc4_pci_priv *pci_priv;
+ spinlock_t lock;
+
+ int dev_id;
+ volatile u32 flags;
+ u32 timer_help;
+
+ unsigned short encoding;
+ unsigned short parity;
+ struct net_device *dev;
+ sync_serial_settings settings;
+ void __iomem *base_addr;
+ u32 __pad __attribute__ ((aligned (4)));
+};
+
+/* GLOBAL registers definitions */
+#define GCMDR 0x00
+#define GSTAR 0x04
+#define GMODE 0x08
+#define IQLENR0 0x0C
+#define IQLENR1 0x10
+#define IQRX0 0x14
+#define IQTX0 0x24
+#define IQCFG 0x3c
+#define FIFOCR1 0x44
+#define FIFOCR2 0x48
+#define FIFOCR3 0x4c
+#define FIFOCR4 0x34
+#define CH0CFG 0x50
+#define CH0BRDA 0x54
+#define CH0BTDA 0x58
+#define CH0FRDA 0x98
+#define CH0FTDA 0xb0
+#define CH0LRDA 0xc8
+#define CH0LTDA 0xe0
+
+/* SCC registers definitions */
+#define SCC_START 0x0100
+#define SCC_OFFSET 0x80
+#define CMDR 0x00
+#define STAR 0x04
+#define CCR0 0x08
+#define CCR1 0x0c
+#define CCR2 0x10
+#define BRR 0x2C
+#define RLCR 0x40
+#define IMR 0x54
+#define ISR 0x58
+
+#define GPDIR 0x0400
+#define GPDATA 0x0404
+#define GPIM 0x0408
+
+/* Bit masks */
+#define EncodingMask 0x00700000
+#define CrcMask 0x00000003
+
+#define IntRxScc0 0x10000000
+#define IntTxScc0 0x01000000
+
+#define TxPollCmd 0x00000400
+#define RxActivate 0x08000000
+#define MTFi 0x04000000
+#define Rdr 0x00400000
+#define Rdt 0x00200000
+#define Idr 0x00100000
+#define Idt 0x00080000
+#define TxSccRes 0x01000000
+#define RxSccRes 0x00010000
+#define TxSizeMax 0x1fff /* Datasheet DS1 - 11.1.1.1 */
+#define RxSizeMax 0x1ffc /* Datasheet DS1 - 11.1.2.1 */
+
+#define Ccr0ClockMask 0x0000003f
+#define Ccr1LoopMask 0x00000200
+#define IsrMask 0x000fffff
+#define BrrExpMask 0x00000f00
+#define BrrMultMask 0x0000003f
+#define EncodingMask 0x00700000
+#define Hold 0x40000000
+#define SccBusy 0x10000000
+#define PowerUp 0x80000000
+#define Vis 0x00001000
+#define FrameOk (FrameVfr | FrameCrc)
+#define FrameVfr 0x80
+#define FrameRdo 0x40
+#define FrameCrc 0x20
+#define FrameRab 0x10
+#define FrameAborted 0x00000200
+#define FrameEnd 0x80000000
+#define DataComplete 0x40000000
+#define LengthCheck 0x00008000
+#define SccEvt 0x02000000
+#define NoAck 0x00000200
+#define Action 0x00000001
+#define HiDesc 0x20000000
+
+/* SCC events */
+#define RxEvt 0xf0000000
+#define TxEvt 0x0f000000
+#define Alls 0x00040000
+#define Xdu 0x00010000
+#define Cts 0x00004000
+#define Xmr 0x00002000
+#define Xpr 0x00001000
+#define Rdo 0x00000080
+#define Rfs 0x00000040
+#define Cd 0x00000004
+#define Rfo 0x00000002
+#define Flex 0x00000001
+
+/* DMA core events */
+#define Cfg 0x00200000
+#define Hi 0x00040000
+#define Fi 0x00020000
+#define Err 0x00010000
+#define Arf 0x00000002
+#define ArAck 0x00000001
+
+/* State flags */
+#define Ready 0x00000000
+#define NeedIDR 0x00000001
+#define NeedIDT 0x00000002
+#define RdoSet 0x00000004
+#define FakeReset 0x00000008
+
+/* Don't mask RDO. Ever. */
+#ifdef DSCC4_POLLING
+#define EventsMask 0xfffeef7f
+#else
+#define EventsMask 0xfffa8f7a
+#endif
+
+/* Functions prototypes */
+static void dscc4_rx_irq(struct dscc4_pci_priv *, struct dscc4_dev_priv *);
+static void dscc4_tx_irq(struct dscc4_pci_priv *, struct dscc4_dev_priv *);
+static int dscc4_found1(struct pci_dev *, void __iomem *ioaddr);
+static int dscc4_init_one(struct pci_dev *, const struct pci_device_id *ent);
+static int dscc4_open(struct net_device *);
+static int dscc4_start_xmit(struct sk_buff *, struct net_device *);
+static int dscc4_close(struct net_device *);
+static int dscc4_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+static int dscc4_init_ring(struct net_device *);
+static void dscc4_release_ring(struct dscc4_dev_priv *);
+static void dscc4_timer(unsigned long);
+static void dscc4_tx_timeout(struct net_device *);
+static irqreturn_t dscc4_irq(int irq, void *dev_id, struct pt_regs *ptregs);
+static int dscc4_hdlc_attach(struct net_device *, unsigned short, unsigned short);
+static int dscc4_set_iface(struct dscc4_dev_priv *, struct net_device *);
+#ifdef DSCC4_POLLING
+static int dscc4_tx_poll(struct dscc4_dev_priv *, struct net_device *);
+#endif
+
+static inline struct dscc4_dev_priv *dscc4_priv(struct net_device *dev)
+{
+ return dev_to_hdlc(dev)->priv;
+}
+
+static inline struct net_device *dscc4_to_dev(struct dscc4_dev_priv *p)
+{
+ return p->dev;
+}
+
+static void scc_patchl(u32 mask, u32 value, struct dscc4_dev_priv *dpriv,
+ struct net_device *dev, int offset)
+{
+ u32 state;
+
+ /* Cf scc_writel for concern regarding thread-safety */
+ state = dpriv->scc_regs[offset >> 2];
+ state &= ~mask;
+ state |= value;
+ dpriv->scc_regs[offset >> 2] = state;
+ writel(state, dpriv->base_addr + SCC_REG_START(dpriv) + offset);
+}
+
+static void scc_writel(u32 bits, struct dscc4_dev_priv *dpriv,
+ struct net_device *dev, int offset)
+{
+ /*
+ * Thread-UNsafe.
+ * As of 2002/02/16, there are no thread racing for access.
+ */
+ dpriv->scc_regs[offset >> 2] = bits;
+ writel(bits, dpriv->base_addr + SCC_REG_START(dpriv) + offset);
+}
+
+static inline u32 scc_readl(struct dscc4_dev_priv *dpriv, int offset)
+{
+ return dpriv->scc_regs[offset >> 2];
+}
+
+static u32 scc_readl_star(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+ /* Cf errata DS5 p.4 */
+ readl(dpriv->base_addr + SCC_REG_START(dpriv) + STAR);
+ return readl(dpriv->base_addr + SCC_REG_START(dpriv) + STAR);
+}
+
+static inline void dscc4_do_tx(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ dpriv->ltda = dpriv->tx_fd_dma +
+ ((dpriv->tx_current-1)%TX_RING_SIZE)*sizeof(struct TxFD);
+ writel(dpriv->ltda, dpriv->base_addr + CH0LTDA + dpriv->dev_id*4);
+ /* Flush posted writes *NOW* */
+ readl(dpriv->base_addr + CH0LTDA + dpriv->dev_id*4);
+}
+
+static inline void dscc4_rx_update(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ dpriv->lrda = dpriv->rx_fd_dma +
+ ((dpriv->rx_dirty - 1)%RX_RING_SIZE)*sizeof(struct RxFD);
+ writel(dpriv->lrda, dpriv->base_addr + CH0LRDA + dpriv->dev_id*4);
+}
+
+static inline unsigned int dscc4_tx_done(struct dscc4_dev_priv *dpriv)
+{
+ return dpriv->tx_current == dpriv->tx_dirty;
+}
+
+static inline unsigned int dscc4_tx_quiescent(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ return readl(dpriv->base_addr + CH0FTDA + dpriv->dev_id*4) == dpriv->ltda;
+}
+
+int state_check(u32 state, struct dscc4_dev_priv *dpriv, struct net_device *dev,
+ const char *msg)
+{
+ int ret = 0;
+
+ if (debug > 1) {
+ if (SOURCE_ID(state) != dpriv->dev_id) {
+ printk(KERN_DEBUG "%s (%s): Source Id=%d, state=%08x\n",
+ dev->name, msg, SOURCE_ID(state), state );
+ ret = -1;
+ }
+ if (state & 0x0df80c00) {
+ printk(KERN_DEBUG "%s (%s): state=%08x (UFO alert)\n",
+ dev->name, msg, state);
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+void dscc4_tx_print(struct net_device *dev, struct dscc4_dev_priv *dpriv,
+ char *msg)
+{
+ printk(KERN_DEBUG "%s: tx_current=%02d tx_dirty=%02d (%s)\n",
+ dev->name, dpriv->tx_current, dpriv->tx_dirty, msg);
+}
+
+static void dscc4_release_ring(struct dscc4_dev_priv *dpriv)
+{
+ struct pci_dev *pdev = dpriv->pci_priv->pdev;
+ struct TxFD *tx_fd = dpriv->tx_fd;
+ struct RxFD *rx_fd = dpriv->rx_fd;
+ struct sk_buff **skbuff;
+ int i;
+
+ pci_free_consistent(pdev, TX_TOTAL_SIZE, tx_fd, dpriv->tx_fd_dma);
+ pci_free_consistent(pdev, RX_TOTAL_SIZE, rx_fd, dpriv->rx_fd_dma);
+
+ skbuff = dpriv->tx_skbuff;
+ for (i = 0; i < TX_RING_SIZE; i++) {
+ if (*skbuff) {
+ pci_unmap_single(pdev, tx_fd->data, (*skbuff)->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb(*skbuff);
+ }
+ skbuff++;
+ tx_fd++;
+ }
+
+ skbuff = dpriv->rx_skbuff;
+ for (i = 0; i < RX_RING_SIZE; i++) {
+ if (*skbuff) {
+ pci_unmap_single(pdev, rx_fd->data,
+ RX_MAX(HDLC_MAX_MRU), PCI_DMA_FROMDEVICE);
+ dev_kfree_skb(*skbuff);
+ }
+ skbuff++;
+ rx_fd++;
+ }
+}
+
+inline int try_get_rx_skb(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+ unsigned int dirty = dpriv->rx_dirty%RX_RING_SIZE;
+ struct RxFD *rx_fd = dpriv->rx_fd + dirty;
+ const int len = RX_MAX(HDLC_MAX_MRU);
+ struct sk_buff *skb;
+ int ret = 0;
+
+ skb = dev_alloc_skb(len);
+ dpriv->rx_skbuff[dirty] = skb;
+ if (skb) {
+ skb->protocol = hdlc_type_trans(skb, dev);
+ rx_fd->data = pci_map_single(dpriv->pci_priv->pdev, skb->data,
+ len, PCI_DMA_FROMDEVICE);
+ } else {
+ rx_fd->data = (u32) NULL;
+ ret = -1;
+ }
+ return ret;
+}
+
+/*
+ * IRQ/thread/whatever safe
+ */
+static int dscc4_wait_ack_cec(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev, char *msg)
+{
+ s8 i = 0;
+
+ do {
+ if (!(scc_readl_star(dpriv, dev) & SccBusy)) {
+ printk(KERN_DEBUG "%s: %s ack (%d try)\n", dev->name,
+ msg, i);
+ goto done;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(10);
+ rmb();
+ } while (++i > 0);
+ printk(KERN_ERR "%s: %s timeout\n", dev->name, msg);
+done:
+ return (i >= 0) ? i : -EAGAIN;
+}
+
+static int dscc4_do_action(struct net_device *dev, char *msg)
+{
+ void __iomem *ioaddr = dscc4_priv(dev)->base_addr;
+ s16 i = 0;
+
+ writel(Action, ioaddr + GCMDR);
+ ioaddr += GSTAR;
+ do {
+ u32 state = readl(ioaddr);
+
+ if (state & ArAck) {
+ printk(KERN_DEBUG "%s: %s ack\n", dev->name, msg);
+ writel(ArAck, ioaddr);
+ goto done;
+ } else if (state & Arf) {
+ printk(KERN_ERR "%s: %s failed\n", dev->name, msg);
+ writel(Arf, ioaddr);
+ i = -1;
+ goto done;
+ }
+ rmb();
+ } while (++i > 0);
+ printk(KERN_ERR "%s: %s timeout\n", dev->name, msg);
+done:
+ return i;
+}
+
+static inline int dscc4_xpr_ack(struct dscc4_dev_priv *dpriv)
+{
+ int cur = dpriv->iqtx_current%IRQ_RING_SIZE;
+ s8 i = 0;
+
+ do {
+ if (!(dpriv->flags & (NeedIDR | NeedIDT)) ||
+ (dpriv->iqtx[cur] & Xpr))
+ break;
+ smp_rmb();
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(10);
+ } while (++i > 0);
+
+ return (i >= 0 ) ? i : -EAGAIN;
+}
+
+#if 0 /* dscc4_{rx/tx}_reset are both unreliable - more tweak needed */
+static void dscc4_rx_reset(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dpriv->pci_priv->lock, flags);
+ /* Cf errata DS5 p.6 */
+ writel(0x00000000, dpriv->base_addr + CH0LRDA + dpriv->dev_id*4);
+ scc_patchl(PowerUp, 0, dpriv, dev, CCR0);
+ readl(dpriv->base_addr + CH0LRDA + dpriv->dev_id*4);
+ writel(MTFi|Rdr, dpriv->base_addr + dpriv->dev_id*0x0c + CH0CFG);
+ writel(Action, dpriv->base_addr + GCMDR);
+ spin_unlock_irqrestore(&dpriv->pci_priv->lock, flags);
+}
+
+#endif
+
+#if 0
+static void dscc4_tx_reset(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+ u16 i = 0;
+
+ /* Cf errata DS5 p.7 */
+ scc_patchl(PowerUp, 0, dpriv, dev, CCR0);
+ scc_writel(0x00050000, dpriv, dev, CCR2);
+ /*
+ * Must be longer than the time required to fill the fifo.
+ */
+ while (!dscc4_tx_quiescent(dpriv, dev) && ++i) {
+ udelay(1);
+ wmb();
+ }
+
+ writel(MTFi|Rdt, dpriv->base_addr + dpriv->dev_id*0x0c + CH0CFG);
+ if (dscc4_do_action(dev, "Rdt") < 0)
+ printk(KERN_ERR "%s: Tx reset failed\n", dev->name);
+}
+#endif
+
+/* TODO: (ab)use this function to refill a completely depleted RX ring. */
+static inline void dscc4_rx_skb(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ struct RxFD *rx_fd = dpriv->rx_fd + dpriv->rx_current%RX_RING_SIZE;
+ struct net_device_stats *stats = hdlc_stats(dev);
+ struct pci_dev *pdev = dpriv->pci_priv->pdev;
+ struct sk_buff *skb;
+ int pkt_len;
+
+ skb = dpriv->rx_skbuff[dpriv->rx_current++%RX_RING_SIZE];
+ if (!skb) {
+ printk(KERN_DEBUG "%s: skb=0 (%s)\n", dev->name, __FUNCTION__);
+ goto refill;
+ }
+ pkt_len = TO_SIZE(rx_fd->state2);
+ pci_unmap_single(pdev, rx_fd->data, RX_MAX(HDLC_MAX_MRU), PCI_DMA_FROMDEVICE);
+ if ((skb->data[--pkt_len] & FrameOk) == FrameOk) {
+ stats->rx_packets++;
+ stats->rx_bytes += pkt_len;
+ skb_put(skb, pkt_len);
+ if (netif_running(dev))
+ skb->protocol = hdlc_type_trans(skb, dev);
+ skb->dev->last_rx = jiffies;
+ netif_rx(skb);
+ } else {
+ if (skb->data[pkt_len] & FrameRdo)
+ stats->rx_fifo_errors++;
+ else if (!(skb->data[pkt_len] | ~FrameCrc))
+ stats->rx_crc_errors++;
+ else if (!(skb->data[pkt_len] | ~(FrameVfr | FrameRab)))
+ stats->rx_length_errors++;
+ else
+ stats->rx_errors++;
+ dev_kfree_skb_irq(skb);
+ }
+refill:
+ while ((dpriv->rx_dirty - dpriv->rx_current) % RX_RING_SIZE) {
+ if (try_get_rx_skb(dpriv, dev) < 0)
+ break;
+ dpriv->rx_dirty++;
+ }
+ dscc4_rx_update(dpriv, dev);
+ rx_fd->state2 = 0x00000000;
+ rx_fd->end = 0xbabeface;
+}
+
+static void dscc4_free1(struct pci_dev *pdev)
+{
+ struct dscc4_pci_priv *ppriv;
+ struct dscc4_dev_priv *root;
+ int i;
+
+ ppriv = pci_get_drvdata(pdev);
+ root = ppriv->root;
+
+ for (i = 0; i < dev_per_card; i++)
+ unregister_hdlc_device(dscc4_to_dev(root + i));
+
+ pci_set_drvdata(pdev, NULL);
+
+ for (i = 0; i < dev_per_card; i++)
+ free_netdev(root[i].dev);
+ kfree(root);
+ kfree(ppriv);
+}
+
+static int __devinit dscc4_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct dscc4_pci_priv *priv;
+ struct dscc4_dev_priv *dpriv;
+ void __iomem *ioaddr;
+ int i, rc;
+
+ printk(KERN_DEBUG "%s", version);
+
+ rc = pci_enable_device(pdev);
+ if (rc < 0)
+ goto out;
+
+ rc = pci_request_region(pdev, 0, "registers");
+ if (rc < 0) {
+ printk(KERN_ERR "%s: can't reserve MMIO region (regs)\n",
+ DRV_NAME);
+ goto err_disable_0;
+ }
+ rc = pci_request_region(pdev, 1, "LBI interface");
+ if (rc < 0) {
+ printk(KERN_ERR "%s: can't reserve MMIO region (lbi)\n",
+ DRV_NAME);
+ goto err_free_mmio_region_1;
+ }
+
+ ioaddr = ioremap(pci_resource_start(pdev, 0),
+ pci_resource_len(pdev, 0));
+ if (!ioaddr) {
+ printk(KERN_ERR "%s: cannot remap MMIO region %lx @ %lx\n",
+ DRV_NAME, pci_resource_len(pdev, 0),
+ pci_resource_start(pdev, 0));
+ rc = -EIO;
+ goto err_free_mmio_regions_2;
+ }
+ printk(KERN_DEBUG "Siemens DSCC4, MMIO at %#lx (regs), %#lx (lbi), IRQ %d\n",
+ pci_resource_start(pdev, 0),
+ pci_resource_start(pdev, 1), pdev->irq);
+
+ /* Cf errata DS5 p.2 */
+ pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xf8);
+ pci_set_master(pdev);
+
+ rc = dscc4_found1(pdev, ioaddr);
+ if (rc < 0)
+ goto err_iounmap_3;
+
+ priv = pci_get_drvdata(pdev);
+
+ rc = request_irq(pdev->irq, dscc4_irq, SA_SHIRQ, DRV_NAME, priv->root);
+ if (rc < 0) {
+ printk(KERN_WARNING "%s: IRQ %d busy\n", DRV_NAME, pdev->irq);
+ goto err_release_4;
+ }
+
+ /* power up/little endian/dma core controlled via lrda/ltda */
+ writel(0x00000001, ioaddr + GMODE);
+ /* Shared interrupt queue */
+ {
+ u32 bits;
+
+ bits = (IRQ_RING_SIZE >> 5) - 1;
+ bits |= bits << 4;
+ bits |= bits << 8;
+ bits |= bits << 16;
+ writel(bits, ioaddr + IQLENR0);
+ }
+ /* Global interrupt queue */
+ writel((u32)(((IRQ_RING_SIZE >> 5) - 1) << 20), ioaddr + IQLENR1);
+ priv->iqcfg = (u32 *) pci_alloc_consistent(pdev,
+ IRQ_RING_SIZE*sizeof(u32), &priv->iqcfg_dma);
+ if (!priv->iqcfg)
+ goto err_free_irq_5;
+ writel(priv->iqcfg_dma, ioaddr + IQCFG);
+
+ rc = -ENOMEM;
+
+ /*
+ * SCC 0-3 private rx/tx irq structures
+ * IQRX/TXi needs to be set soon. Learned it the hard way...
+ */
+ for (i = 0; i < dev_per_card; i++) {
+ dpriv = priv->root + i;
+ dpriv->iqtx = (u32 *) pci_alloc_consistent(pdev,
+ IRQ_RING_SIZE*sizeof(u32), &dpriv->iqtx_dma);
+ if (!dpriv->iqtx)
+ goto err_free_iqtx_6;
+ writel(dpriv->iqtx_dma, ioaddr + IQTX0 + i*4);
+ }
+ for (i = 0; i < dev_per_card; i++) {
+ dpriv = priv->root + i;
+ dpriv->iqrx = (u32 *) pci_alloc_consistent(pdev,
+ IRQ_RING_SIZE*sizeof(u32), &dpriv->iqrx_dma);
+ if (!dpriv->iqrx)
+ goto err_free_iqrx_7;
+ writel(dpriv->iqrx_dma, ioaddr + IQRX0 + i*4);
+ }
+
+ /* Cf application hint. Beware of hard-lock condition on threshold. */
+ writel(0x42104000, ioaddr + FIFOCR1);
+ //writel(0x9ce69800, ioaddr + FIFOCR2);
+ writel(0xdef6d800, ioaddr + FIFOCR2);
+ //writel(0x11111111, ioaddr + FIFOCR4);
+ writel(0x18181818, ioaddr + FIFOCR4);
+ // FIXME: should depend on the chipset revision
+ writel(0x0000000e, ioaddr + FIFOCR3);
+
+ writel(0xff200001, ioaddr + GCMDR);
+
+ rc = 0;
+out:
+ return rc;
+
+err_free_iqrx_7:
+ while (--i >= 0) {
+ dpriv = priv->root + i;
+ pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+ dpriv->iqrx, dpriv->iqrx_dma);
+ }
+ i = dev_per_card;
+err_free_iqtx_6:
+ while (--i >= 0) {
+ dpriv = priv->root + i;
+ pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+ dpriv->iqtx, dpriv->iqtx_dma);
+ }
+ pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32), priv->iqcfg,
+ priv->iqcfg_dma);
+err_free_irq_5:
+ free_irq(pdev->irq, priv->root);
+err_release_4:
+ dscc4_free1(pdev);
+err_iounmap_3:
+ iounmap (ioaddr);
+err_free_mmio_regions_2:
+ pci_release_region(pdev, 1);
+err_free_mmio_region_1:
+ pci_release_region(pdev, 0);
+err_disable_0:
+ pci_disable_device(pdev);
+ goto out;
+};
+
+/*
+ * Let's hope the default values are decent enough to protect my
+ * feet from the user's gun - Ueimor
+ */
+static void dscc4_init_registers(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ /* No interrupts, SCC core disabled. Let's relax */
+ scc_writel(0x00000000, dpriv, dev, CCR0);
+
+ scc_writel(LengthCheck | (HDLC_MAX_MRU >> 5), dpriv, dev, RLCR);
+
+ /*
+ * No address recognition/crc-CCITT/cts enabled
+ * Shared flags transmission disabled - cf errata DS5 p.11
+ * Carrier detect disabled - cf errata p.14
+ * FIXME: carrier detection/polarity may be handled more gracefully.
+ */
+ scc_writel(0x02408000, dpriv, dev, CCR1);
+
+ /* crc not forwarded - Cf errata DS5 p.11 */
+ scc_writel(0x00050008 & ~RxActivate, dpriv, dev, CCR2);
+ // crc forwarded
+ //scc_writel(0x00250008 & ~RxActivate, dpriv, dev, CCR2);
+}
+
+static inline int dscc4_set_quartz(struct dscc4_dev_priv *dpriv, int hz)
+{
+ int ret = 0;
+
+ if ((hz < 0) || (hz > DSCC4_HZ_MAX))
+ ret = -EOPNOTSUPP;
+ else
+ dpriv->pci_priv->xtal_hz = hz;
+
+ return ret;
+}
+
+static int dscc4_found1(struct pci_dev *pdev, void __iomem *ioaddr)
+{
+ struct dscc4_pci_priv *ppriv;
+ struct dscc4_dev_priv *root;
+ int i, ret = -ENOMEM;
+
+ root = kmalloc(dev_per_card*sizeof(*root), GFP_KERNEL);
+ if (!root) {
+ printk(KERN_ERR "%s: can't allocate data\n", DRV_NAME);
+ goto err_out;
+ }
+ memset(root, 0, dev_per_card*sizeof(*root));
+
+ for (i = 0; i < dev_per_card; i++) {
+ root[i].dev = alloc_hdlcdev(root + i);
+ if (!root[i].dev)
+ goto err_free_dev;
+ }
+
+ ppriv = kmalloc(sizeof(*ppriv), GFP_KERNEL);
+ if (!ppriv) {
+ printk(KERN_ERR "%s: can't allocate private data\n", DRV_NAME);
+ goto err_free_dev;
+ }
+ memset(ppriv, 0, sizeof(struct dscc4_pci_priv));
+
+ ppriv->root = root;
+ spin_lock_init(&ppriv->lock);
+
+ for (i = 0; i < dev_per_card; i++) {
+ struct dscc4_dev_priv *dpriv = root + i;
+ struct net_device *d = dscc4_to_dev(dpriv);
+ hdlc_device *hdlc = dev_to_hdlc(d);
+
+ d->base_addr = (unsigned long)ioaddr;
+ d->init = NULL;
+ d->irq = pdev->irq;
+ d->open = dscc4_open;
+ d->stop = dscc4_close;
+ d->set_multicast_list = NULL;
+ d->do_ioctl = dscc4_ioctl;
+ d->tx_timeout = dscc4_tx_timeout;
+ d->watchdog_timeo = TX_TIMEOUT;
+ SET_MODULE_OWNER(d);
+ SET_NETDEV_DEV(d, &pdev->dev);
+
+ dpriv->dev_id = i;
+ dpriv->pci_priv = ppriv;
+ dpriv->base_addr = ioaddr;
+ spin_lock_init(&dpriv->lock);
+
+ hdlc->xmit = dscc4_start_xmit;
+ hdlc->attach = dscc4_hdlc_attach;
+
+ dscc4_init_registers(dpriv, d);
+ dpriv->parity = PARITY_CRC16_PR0_CCITT;
+ dpriv->encoding = ENCODING_NRZ;
+
+ ret = dscc4_init_ring(d);
+ if (ret < 0)
+ goto err_unregister;
+
+ ret = register_hdlc_device(d);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: unable to register\n", DRV_NAME);
+ dscc4_release_ring(dpriv);
+ goto err_unregister;
+ }
+ }
+
+ ret = dscc4_set_quartz(root, quartz);
+ if (ret < 0)
+ goto err_unregister;
+
+ pci_set_drvdata(pdev, ppriv);
+ return ret;
+
+err_unregister:
+ while (i-- > 0) {
+ dscc4_release_ring(root + i);
+ unregister_hdlc_device(dscc4_to_dev(root + i));
+ }
+ kfree(ppriv);
+ i = dev_per_card;
+err_free_dev:
+ while (i-- > 0)
+ free_netdev(root[i].dev);
+ kfree(root);
+err_out:
+ return ret;
+};
+
+/* FIXME: get rid of the unneeded code */
+static void dscc4_timer(unsigned long data)
+{
+ struct net_device *dev = (struct net_device *)data;
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+// struct dscc4_pci_priv *ppriv;
+
+ goto done;
+done:
+ dpriv->timer.expires = jiffies + TX_TIMEOUT;
+ add_timer(&dpriv->timer);
+}
+
+static void dscc4_tx_timeout(struct net_device *dev)
+{
+ /* FIXME: something is missing there */
+}
+
+static int dscc4_loopback_check(struct dscc4_dev_priv *dpriv)
+{
+ sync_serial_settings *settings = &dpriv->settings;
+
+ if (settings->loopback && (settings->clock_type != CLOCK_INT)) {
+ struct net_device *dev = dscc4_to_dev(dpriv);
+
+ printk(KERN_INFO "%s: loopback requires clock\n", dev->name);
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_DSCC4_PCI_RST
+/*
+ * Some DSCC4-based cards wires the GPIO port and the PCI #RST pin together
+ * so as to provide a safe way to reset the asic while not the whole machine
+ * rebooting.
+ *
+ * This code doesn't need to be efficient. Keep It Simple
+ */
+static void dscc4_pci_reset(struct pci_dev *pdev, void __iomem *ioaddr)
+{
+ int i;
+
+ down(&dscc4_sem);
+ for (i = 0; i < 16; i++)
+ pci_read_config_dword(pdev, i << 2, dscc4_pci_config_store + i);
+
+ /* Maximal LBI clock divider (who cares ?) and whole GPIO range. */
+ writel(0x001c0000, ioaddr + GMODE);
+ /* Configure GPIO port as output */
+ writel(0x0000ffff, ioaddr + GPDIR);
+ /* Disable interruption */
+ writel(0x0000ffff, ioaddr + GPIM);
+
+ writel(0x0000ffff, ioaddr + GPDATA);
+ writel(0x00000000, ioaddr + GPDATA);
+
+ /* Flush posted writes */
+ readl(ioaddr + GSTAR);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(10);
+
+ for (i = 0; i < 16; i++)
+ pci_write_config_dword(pdev, i << 2, dscc4_pci_config_store[i]);
+ up(&dscc4_sem);
+}
+#else
+#define dscc4_pci_reset(pdev,ioaddr) do {} while (0)
+#endif /* CONFIG_DSCC4_PCI_RST */
+
+static int dscc4_open(struct net_device *dev)
+{
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+ struct dscc4_pci_priv *ppriv;
+ int ret = -EAGAIN;
+
+ if ((dscc4_loopback_check(dpriv) < 0) || !dev->hard_start_xmit)
+ goto err;
+
+ if ((ret = hdlc_open(dev)))
+ goto err;
+
+ ppriv = dpriv->pci_priv;
+
+ /*
+ * Due to various bugs, there is no way to reliably reset a
+ * specific port (manufacturer's dependant special PCI #RST wiring
+ * apart: it affects all ports). Thus the device goes in the best
+ * silent mode possible at dscc4_close() time and simply claims to
+ * be up if it's opened again. It still isn't possible to change
+ * the HDLC configuration without rebooting but at least the ports
+ * can be up/down ifconfig'ed without killing the host.
+ */
+ if (dpriv->flags & FakeReset) {
+ dpriv->flags &= ~FakeReset;
+ scc_patchl(0, PowerUp, dpriv, dev, CCR0);
+ scc_patchl(0, 0x00050000, dpriv, dev, CCR2);
+ scc_writel(EventsMask, dpriv, dev, IMR);
+ printk(KERN_INFO "%s: up again.\n", dev->name);
+ goto done;
+ }
+
+ /* IDT+IDR during XPR */
+ dpriv->flags = NeedIDR | NeedIDT;
+
+ scc_patchl(0, PowerUp | Vis, dpriv, dev, CCR0);
+
+ /*
+ * The following is a bit paranoid...
+ *
+ * NB: the datasheet "...CEC will stay active if the SCC is in
+ * power-down mode or..." and CCR2.RAC = 1 are two different
+ * situations.
+ */
+ if (scc_readl_star(dpriv, dev) & SccBusy) {
+ printk(KERN_ERR "%s busy. Try later\n", dev->name);
+ ret = -EAGAIN;
+ goto err_out;
+ } else
+ printk(KERN_INFO "%s: available. Good\n", dev->name);
+
+ scc_writel(EventsMask, dpriv, dev, IMR);
+
+ /* Posted write is flushed in the wait_ack loop */
+ scc_writel(TxSccRes | RxSccRes, dpriv, dev, CMDR);
+
+ if ((ret = dscc4_wait_ack_cec(dpriv, dev, "Cec")) < 0)
+ goto err_disable_scc_events;
+
+ /*
+ * I would expect XPR near CE completion (before ? after ?).
+ * At worst, this code won't see a late XPR and people
+ * will have to re-issue an ifconfig (this is harmless).
+ * WARNING, a really missing XPR usually means a hardware
+ * reset is needed. Suggestions anyone ?
+ */
+ if ((ret = dscc4_xpr_ack(dpriv)) < 0) {
+ printk(KERN_ERR "%s: %s timeout\n", DRV_NAME, "XPR");
+ goto err_disable_scc_events;
+ }
+
+ if (debug > 2)
+ dscc4_tx_print(dev, dpriv, "Open");
+
+done:
+ netif_start_queue(dev);
+
+ init_timer(&dpriv->timer);
+ dpriv->timer.expires = jiffies + 10*HZ;
+ dpriv->timer.data = (unsigned long)dev;
+ dpriv->timer.function = &dscc4_timer;
+ add_timer(&dpriv->timer);
+ netif_carrier_on(dev);
+
+ return 0;
+
+err_disable_scc_events:
+ scc_writel(0xffffffff, dpriv, dev, IMR);
+ scc_patchl(PowerUp | Vis, 0, dpriv, dev, CCR0);
+err_out:
+ hdlc_close(dev);
+err:
+ return ret;
+}
+
+#ifdef DSCC4_POLLING
+static int dscc4_tx_poll(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+ /* FIXME: it's gonna be easy (TM), for sure */
+}
+#endif /* DSCC4_POLLING */
+
+static int dscc4_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+ struct dscc4_pci_priv *ppriv = dpriv->pci_priv;
+ struct TxFD *tx_fd;
+ int next;
+
+ next = dpriv->tx_current%TX_RING_SIZE;
+ dpriv->tx_skbuff[next] = skb;
+ tx_fd = dpriv->tx_fd + next;
+ tx_fd->state = FrameEnd | TO_STATE_TX(skb->len);
+ tx_fd->data = pci_map_single(ppriv->pdev, skb->data, skb->len,
+ PCI_DMA_TODEVICE);
+ tx_fd->complete = 0x00000000;
+ tx_fd->jiffies = jiffies;
+ mb();
+
+#ifdef DSCC4_POLLING
+ spin_lock(&dpriv->lock);
+ while (dscc4_tx_poll(dpriv, dev));
+ spin_unlock(&dpriv->lock);
+#endif
+
+ dev->trans_start = jiffies;
+
+ if (debug > 2)
+ dscc4_tx_print(dev, dpriv, "Xmit");
+ /* To be cleaned(unsigned int)/optimized. Later, ok ? */
+ if (!((++dpriv->tx_current - dpriv->tx_dirty)%TX_RING_SIZE))
+ netif_stop_queue(dev);
+
+ if (dscc4_tx_quiescent(dpriv, dev))
+ dscc4_do_tx(dpriv, dev);
+
+ return 0;
+}
+
+static int dscc4_close(struct net_device *dev)
+{
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+
+ del_timer_sync(&dpriv->timer);
+ netif_stop_queue(dev);
+
+ scc_patchl(PowerUp | Vis, 0, dpriv, dev, CCR0);
+ scc_patchl(0x00050000, 0, dpriv, dev, CCR2);
+ scc_writel(0xffffffff, dpriv, dev, IMR);
+
+ dpriv->flags |= FakeReset;
+
+ hdlc_close(dev);
+
+ return 0;
+}
+
+static inline int dscc4_check_clock_ability(int port)
+{
+ int ret = 0;
+
+#ifdef CONFIG_DSCC4_PCISYNC
+ if (port >= 2)
+ ret = -1;
+#endif
+ return ret;
+}
+
+/*
+ * DS1 p.137: "There are a total of 13 different clocking modes..."
+ * ^^
+ * Design choices:
+ * - by default, assume a clock is provided on pin RxClk/TxClk (clock mode 0a).
+ * Clock mode 3b _should_ work but the testing seems to make this point
+ * dubious (DIY testing requires setting CCR0 at 0x00000033).
+ * This is supposed to provide least surprise "DTE like" behavior.
+ * - if line rate is specified, clocks are assumed to be locally generated.
+ * A quartz must be available (on pin XTAL1). Modes 6b/7b are used. Choosing
+ * between these it automagically done according on the required frequency
+ * scaling. Of course some rounding may take place.
+ * - no high speed mode (40Mb/s). May be trivial to do but I don't have an
+ * appropriate external clocking device for testing.
+ * - no time-slot/clock mode 5: shameless lazyness.
+ *
+ * The clock signals wiring can be (is ?) manufacturer dependant. Good luck.
+ *
+ * BIG FAT WARNING: if the device isn't provided enough clocking signal, it
+ * won't pass the init sequence. For example, straight back-to-back DTE without
+ * external clock will fail when dscc4_open() (<- 'ifconfig hdlcx xxx') is
+ * called.
+ *
+ * Typos lurk in datasheet (missing divier in clock mode 7a figure 51 p.153
+ * DS0 for example)
+ *
+ * Clock mode related bits of CCR0:
+ * +------------ TOE: output TxClk (0b/2b/3a/3b/6b/7a/7b only)
+ * | +---------- SSEL: sub-mode select 0 -> a, 1 -> b
+ * | | +-------- High Speed: say 0
+ * | | | +-+-+-- Clock Mode: 0..7
+ * | | | | | |
+ * -+-+-+-+-+-+-+-+
+ * x|x|5|4|3|2|1|0| lower bits
+ *
+ * Division factor of BRR: k = (N+1)x2^M (total divider = 16xk in mode 6b)
+ * +-+-+-+------------------ M (0..15)
+ * | | | | +-+-+-+-+-+-- N (0..63)
+ * 0 0 0 0 | | | | 0 0 | | | | | |
+ * ...-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * f|e|d|c|b|a|9|8|7|6|5|4|3|2|1|0| lower bits
+ *
+ */
+static int dscc4_set_clock(struct net_device *dev, u32 *bps, u32 *state)
+{
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+ int ret = -1;
+ u32 brr;
+
+ *state &= ~Ccr0ClockMask;
+ if (*bps) { /* Clock generated - required for DCE */
+ u32 n = 0, m = 0, divider;
+ int xtal;
+
+ xtal = dpriv->pci_priv->xtal_hz;
+ if (!xtal)
+ goto done;
+ if (dscc4_check_clock_ability(dpriv->dev_id) < 0)
+ goto done;
+ divider = xtal / *bps;
+ if (divider > BRR_DIVIDER_MAX) {
+ divider >>= 4;
+ *state |= 0x00000036; /* Clock mode 6b (BRG/16) */
+ } else
+ *state |= 0x00000037; /* Clock mode 7b (BRG) */
+ if (divider >> 22) {
+ n = 63;
+ m = 15;
+ } else if (divider) {
+ /* Extraction of the 6 highest weighted bits */
+ m = 0;
+ while (0xffffffc0 & divider) {
+ m++;
+ divider >>= 1;
+ }
+ n = divider;
+ }
+ brr = (m << 8) | n;
+ divider = n << m;
+ if (!(*state & 0x00000001)) /* ?b mode mask => clock mode 6b */
+ divider <<= 4;
+ *bps = xtal / divider;
+ } else {
+ /*
+ * External clock - DTE
+ * "state" already reflects Clock mode 0a (CCR0 = 0xzzzzzz00).
+ * Nothing more to be done
+ */
+ brr = 0;
+ }
+ scc_writel(brr, dpriv, dev, BRR);
+ ret = 0;
+done:
+ return ret;
+}
+
+static int dscc4_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+ const size_t size = sizeof(dpriv->settings);
+ int ret = 0;
+
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+
+ if (cmd != SIOCWANDEV)
+ return -EOPNOTSUPP;
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(line, &dpriv->settings, size))
+ return -EFAULT;
+ break;
+
+ case IF_IFACE_SYNC_SERIAL:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (dpriv->flags & FakeReset) {
+ printk(KERN_INFO "%s: please reset the device"
+ " before this command\n", dev->name);
+ return -EPERM;
+ }
+ if (copy_from_user(&dpriv->settings, line, size))
+ return -EFAULT;
+ ret = dscc4_set_iface(dpriv, dev);
+ break;
+
+ default:
+ ret = hdlc_ioctl(dev, ifr, cmd);
+ break;
+ }
+
+ return ret;
+}
+
+static int dscc4_match(struct thingie *p, int value)
+{
+ int i;
+
+ for (i = 0; p[i].define != -1; i++) {
+ if (value == p[i].define)
+ break;
+ }
+ if (p[i].define == -1)
+ return -1;
+ else
+ return i;
+}
+
+static int dscc4_clock_setting(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ sync_serial_settings *settings = &dpriv->settings;
+ int ret = -EOPNOTSUPP;
+ u32 bps, state;
+
+ bps = settings->clock_rate;
+ state = scc_readl(dpriv, CCR0);
+ if (dscc4_set_clock(dev, &bps, &state) < 0)
+ goto done;
+ if (bps) { /* DCE */
+ printk(KERN_DEBUG "%s: generated RxClk (DCE)\n", dev->name);
+ if (settings->clock_rate != bps) {
+ printk(KERN_DEBUG "%s: clock adjusted (%08d -> %08d)\n",
+ dev->name, settings->clock_rate, bps);
+ settings->clock_rate = bps;
+ }
+ } else { /* DTE */
+ state |= PowerUp | Vis;
+ printk(KERN_DEBUG "%s: external RxClk (DTE)\n", dev->name);
+ }
+ scc_writel(state, dpriv, dev, CCR0);
+ ret = 0;
+done:
+ return ret;
+}
+
+static int dscc4_encoding_setting(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ struct thingie encoding[] = {
+ { ENCODING_NRZ, 0x00000000 },
+ { ENCODING_NRZI, 0x00200000 },
+ { ENCODING_FM_MARK, 0x00400000 },
+ { ENCODING_FM_SPACE, 0x00500000 },
+ { ENCODING_MANCHESTER, 0x00600000 },
+ { -1, 0}
+ };
+ int i, ret = 0;
+
+ i = dscc4_match(encoding, dpriv->encoding);
+ if (i >= 0)
+ scc_patchl(EncodingMask, encoding[i].bits, dpriv, dev, CCR0);
+ else
+ ret = -EOPNOTSUPP;
+ return ret;
+}
+
+static int dscc4_loopback_setting(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ sync_serial_settings *settings = &dpriv->settings;
+ u32 state;
+
+ state = scc_readl(dpriv, CCR1);
+ if (settings->loopback) {
+ printk(KERN_DEBUG "%s: loopback\n", dev->name);
+ state |= 0x00000100;
+ } else {
+ printk(KERN_DEBUG "%s: normal\n", dev->name);
+ state &= ~0x00000100;
+ }
+ scc_writel(state, dpriv, dev, CCR1);
+ return 0;
+}
+
+static int dscc4_crc_setting(struct dscc4_dev_priv *dpriv,
+ struct net_device *dev)
+{
+ struct thingie crc[] = {
+ { PARITY_CRC16_PR0_CCITT, 0x00000010 },
+ { PARITY_CRC16_PR1_CCITT, 0x00000000 },
+ { PARITY_CRC32_PR0_CCITT, 0x00000011 },
+ { PARITY_CRC32_PR1_CCITT, 0x00000001 }
+ };
+ int i, ret = 0;
+
+ i = dscc4_match(crc, dpriv->parity);
+ if (i >= 0)
+ scc_patchl(CrcMask, crc[i].bits, dpriv, dev, CCR1);
+ else
+ ret = -EOPNOTSUPP;
+ return ret;
+}
+
+static int dscc4_set_iface(struct dscc4_dev_priv *dpriv, struct net_device *dev)
+{
+ struct {
+ int (*action)(struct dscc4_dev_priv *, struct net_device *);
+ } *p, do_setting[] = {
+ { dscc4_encoding_setting },
+ { dscc4_clock_setting },
+ { dscc4_loopback_setting },
+ { dscc4_crc_setting },
+ { NULL }
+ };
+ int ret = 0;
+
+ for (p = do_setting; p->action; p++) {
+ if ((ret = p->action(dpriv, dev)) < 0)
+ break;
+ }
+ return ret;
+}
+
+static irqreturn_t dscc4_irq(int irq, void *token, struct pt_regs *ptregs)
+{
+ struct dscc4_dev_priv *root = token;
+ struct dscc4_pci_priv *priv;
+ struct net_device *dev;
+ void __iomem *ioaddr;
+ u32 state;
+ unsigned long flags;
+ int i, handled = 1;
+
+ priv = root->pci_priv;
+ dev = dscc4_to_dev(root);
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ ioaddr = root->base_addr;
+
+ state = readl(ioaddr + GSTAR);
+ if (!state) {
+ handled = 0;
+ goto out;
+ }
+ if (debug > 3)
+ printk(KERN_DEBUG "%s: GSTAR = 0x%08x\n", DRV_NAME, state);
+ writel(state, ioaddr + GSTAR);
+
+ if (state & Arf) {
+ printk(KERN_ERR "%s: failure (Arf). Harass the maintener\n",
+ dev->name);
+ goto out;
+ }
+ state &= ~ArAck;
+ if (state & Cfg) {
+ if (debug > 0)
+ printk(KERN_DEBUG "%s: CfgIV\n", DRV_NAME);
+ if (priv->iqcfg[priv->cfg_cur++%IRQ_RING_SIZE] & Arf)
+ printk(KERN_ERR "%s: %s failed\n", dev->name, "CFG");
+ if (!(state &= ~Cfg))
+ goto out;
+ }
+ if (state & RxEvt) {
+ i = dev_per_card - 1;
+ do {
+ dscc4_rx_irq(priv, root + i);
+ } while (--i >= 0);
+ state &= ~RxEvt;
+ }
+ if (state & TxEvt) {
+ i = dev_per_card - 1;
+ do {
+ dscc4_tx_irq(priv, root + i);
+ } while (--i >= 0);
+ state &= ~TxEvt;
+ }
+out:
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return IRQ_RETVAL(handled);
+}
+
+static void dscc4_tx_irq(struct dscc4_pci_priv *ppriv,
+ struct dscc4_dev_priv *dpriv)
+{
+ struct net_device *dev = dscc4_to_dev(dpriv);
+ u32 state;
+ int cur, loop = 0;
+
+try:
+ cur = dpriv->iqtx_current%IRQ_RING_SIZE;
+ state = dpriv->iqtx[cur];
+ if (!state) {
+ if (debug > 4)
+ printk(KERN_DEBUG "%s: Tx ISR = 0x%08x\n", dev->name,
+ state);
+ if ((debug > 1) && (loop > 1))
+ printk(KERN_DEBUG "%s: Tx irq loop=%d\n", dev->name, loop);
+ if (loop && netif_queue_stopped(dev))
+ if ((dpriv->tx_current - dpriv->tx_dirty)%TX_RING_SIZE)
+ netif_wake_queue(dev);
+
+ if (netif_running(dev) && dscc4_tx_quiescent(dpriv, dev) &&
+ !dscc4_tx_done(dpriv))
+ dscc4_do_tx(dpriv, dev);
+ return;
+ }
+ loop++;
+ dpriv->iqtx[cur] = 0;
+ dpriv->iqtx_current++;
+
+ if (state_check(state, dpriv, dev, "Tx") < 0)
+ return;
+
+ if (state & SccEvt) {
+ if (state & Alls) {
+ struct net_device_stats *stats = hdlc_stats(dev);
+ struct sk_buff *skb;
+ struct TxFD *tx_fd;
+
+ if (debug > 2)
+ dscc4_tx_print(dev, dpriv, "Alls");
+ /*
+ * DataComplete can't be trusted for Tx completion.
+ * Cf errata DS5 p.8
+ */
+ cur = dpriv->tx_dirty%TX_RING_SIZE;
+ tx_fd = dpriv->tx_fd + cur;
+ skb = dpriv->tx_skbuff[cur];
+ if (skb) {
+ pci_unmap_single(ppriv->pdev, tx_fd->data,
+ skb->len, PCI_DMA_TODEVICE);
+ if (tx_fd->state & FrameEnd) {
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+ }
+ dev_kfree_skb_irq(skb);
+ dpriv->tx_skbuff[cur] = NULL;
+ ++dpriv->tx_dirty;
+ } else {
+ if (debug > 1)
+ printk(KERN_ERR "%s Tx: NULL skb %d\n",
+ dev->name, cur);
+ }
+ /*
+ * If the driver ends sending crap on the wire, it
+ * will be way easier to diagnose than the (not so)
+ * random freeze induced by null sized tx frames.
+ */
+ tx_fd->data = tx_fd->next;
+ tx_fd->state = FrameEnd | TO_STATE_TX(2*DUMMY_SKB_SIZE);
+ tx_fd->complete = 0x00000000;
+ tx_fd->jiffies = 0;
+
+ if (!(state &= ~Alls))
+ goto try;
+ }
+ /*
+ * Transmit Data Underrun
+ */
+ if (state & Xdu) {
+ printk(KERN_ERR "%s: XDU. Ask maintainer\n", DRV_NAME);
+ dpriv->flags = NeedIDT;
+ /* Tx reset */
+ writel(MTFi | Rdt,
+ dpriv->base_addr + 0x0c*dpriv->dev_id + CH0CFG);
+ writel(Action, dpriv->base_addr + GCMDR);
+ return;
+ }
+ if (state & Cts) {
+ printk(KERN_INFO "%s: CTS transition\n", dev->name);
+ if (!(state &= ~Cts)) /* DEBUG */
+ goto try;
+ }
+ if (state & Xmr) {
+ /* Frame needs to be sent again - FIXME */
+ printk(KERN_ERR "%s: Xmr. Ask maintainer\n", DRV_NAME);
+ if (!(state &= ~Xmr)) /* DEBUG */
+ goto try;
+ }
+ if (state & Xpr) {
+ void __iomem *scc_addr;
+ unsigned long ring;
+ int i;
+
+ /*
+ * - the busy condition happens (sometimes);
+ * - it doesn't seem to make the handler unreliable.
+ */
+ for (i = 1; i; i <<= 1) {
+ if (!(scc_readl_star(dpriv, dev) & SccBusy))
+ break;
+ }
+ if (!i)
+ printk(KERN_INFO "%s busy in irq\n", dev->name);
+
+ scc_addr = dpriv->base_addr + 0x0c*dpriv->dev_id;
+ /* Keep this order: IDT before IDR */
+ if (dpriv->flags & NeedIDT) {
+ if (debug > 2)
+ dscc4_tx_print(dev, dpriv, "Xpr");
+ ring = dpriv->tx_fd_dma +
+ (dpriv->tx_dirty%TX_RING_SIZE)*
+ sizeof(struct TxFD);
+ writel(ring, scc_addr + CH0BTDA);
+ dscc4_do_tx(dpriv, dev);
+ writel(MTFi | Idt, scc_addr + CH0CFG);
+ if (dscc4_do_action(dev, "IDT") < 0)
+ goto err_xpr;
+ dpriv->flags &= ~NeedIDT;
+ }
+ if (dpriv->flags & NeedIDR) {
+ ring = dpriv->rx_fd_dma +
+ (dpriv->rx_current%RX_RING_SIZE)*
+ sizeof(struct RxFD);
+ writel(ring, scc_addr + CH0BRDA);
+ dscc4_rx_update(dpriv, dev);
+ writel(MTFi | Idr, scc_addr + CH0CFG);
+ if (dscc4_do_action(dev, "IDR") < 0)
+ goto err_xpr;
+ dpriv->flags &= ~NeedIDR;
+ smp_wmb();
+ /* Activate receiver and misc */
+ scc_writel(0x08050008, dpriv, dev, CCR2);
+ }
+ err_xpr:
+ if (!(state &= ~Xpr))
+ goto try;
+ }
+ if (state & Cd) {
+ if (debug > 0)
+ printk(KERN_INFO "%s: CD transition\n", dev->name);
+ if (!(state &= ~Cd)) /* DEBUG */
+ goto try;
+ }
+ } else { /* ! SccEvt */
+ if (state & Hi) {
+#ifdef DSCC4_POLLING
+ while (!dscc4_tx_poll(dpriv, dev));
+#endif
+ printk(KERN_INFO "%s: Tx Hi\n", dev->name);
+ state &= ~Hi;
+ }
+ if (state & Err) {
+ printk(KERN_INFO "%s: Tx ERR\n", dev->name);
+ hdlc_stats(dev)->tx_errors++;
+ state &= ~Err;
+ }
+ }
+ goto try;
+}
+
+static void dscc4_rx_irq(struct dscc4_pci_priv *priv,
+ struct dscc4_dev_priv *dpriv)
+{
+ struct net_device *dev = dscc4_to_dev(dpriv);
+ u32 state;
+ int cur;
+
+try:
+ cur = dpriv->iqrx_current%IRQ_RING_SIZE;
+ state = dpriv->iqrx[cur];
+ if (!state)
+ return;
+ dpriv->iqrx[cur] = 0;
+ dpriv->iqrx_current++;
+
+ if (state_check(state, dpriv, dev, "Rx") < 0)
+ return;
+
+ if (!(state & SccEvt)){
+ struct RxFD *rx_fd;
+
+ if (debug > 4)
+ printk(KERN_DEBUG "%s: Rx ISR = 0x%08x\n", dev->name,
+ state);
+ state &= 0x00ffffff;
+ if (state & Err) { /* Hold or reset */
+ printk(KERN_DEBUG "%s: Rx ERR\n", dev->name);
+ cur = dpriv->rx_current%RX_RING_SIZE;
+ rx_fd = dpriv->rx_fd + cur;
+ /*
+ * Presume we're not facing a DMAC receiver reset.
+ * As We use the rx size-filtering feature of the
+ * DSCC4, the beginning of a new frame is waiting in
+ * the rx fifo. I bet a Receive Data Overflow will
+ * happen most of time but let's try and avoid it.
+ * Btw (as for RDO) if one experiences ERR whereas
+ * the system looks rather idle, there may be a
+ * problem with latency. In this case, increasing
+ * RX_RING_SIZE may help.
+ */
+ //while (dpriv->rx_needs_refill) {
+ while (!(rx_fd->state1 & Hold)) {
+ rx_fd++;
+ cur++;
+ if (!(cur = cur%RX_RING_SIZE))
+ rx_fd = dpriv->rx_fd;
+ }
+ //dpriv->rx_needs_refill--;
+ try_get_rx_skb(dpriv, dev);
+ if (!rx_fd->data)
+ goto try;
+ rx_fd->state1 &= ~Hold;
+ rx_fd->state2 = 0x00000000;
+ rx_fd->end = 0xbabeface;
+ //}
+ goto try;
+ }
+ if (state & Fi) {
+ dscc4_rx_skb(dpriv, dev);
+ goto try;
+ }
+ if (state & Hi ) { /* HI bit */
+ printk(KERN_INFO "%s: Rx Hi\n", dev->name);
+ state &= ~Hi;
+ goto try;
+ }
+ } else { /* SccEvt */
+ if (debug > 1) {
+ //FIXME: verifier la presence de tous les evenements
+ static struct {
+ u32 mask;
+ const char *irq_name;
+ } evts[] = {
+ { 0x00008000, "TIN"},
+ { 0x00000020, "RSC"},
+ { 0x00000010, "PCE"},
+ { 0x00000008, "PLLA"},
+ { 0, NULL}
+ }, *evt;
+
+ for (evt = evts; evt->irq_name; evt++) {
+ if (state & evt->mask) {
+ printk(KERN_DEBUG "%s: %s\n",
+ dev->name, evt->irq_name);
+ if (!(state &= ~evt->mask))
+ goto try;
+ }
+ }
+ } else {
+ if (!(state &= ~0x0000c03c))
+ goto try;
+ }
+ if (state & Cts) {
+ printk(KERN_INFO "%s: CTS transition\n", dev->name);
+ if (!(state &= ~Cts)) /* DEBUG */
+ goto try;
+ }
+ /*
+ * Receive Data Overflow (FIXME: fscked)
+ */
+ if (state & Rdo) {
+ struct RxFD *rx_fd;
+ void __iomem *scc_addr;
+ int cur;
+
+ //if (debug)
+ // dscc4_rx_dump(dpriv);
+ scc_addr = dpriv->base_addr + 0x0c*dpriv->dev_id;
+
+ scc_patchl(RxActivate, 0, dpriv, dev, CCR2);
+ /*
+ * This has no effect. Why ?
+ * ORed with TxSccRes, one sees the CFG ack (for
+ * the TX part only).
+ */
+ scc_writel(RxSccRes, dpriv, dev, CMDR);
+ dpriv->flags |= RdoSet;
+
+ /*
+ * Let's try and save something in the received data.
+ * rx_current must be incremented at least once to
+ * avoid HOLD in the BRDA-to-be-pointed desc.
+ */
+ do {
+ cur = dpriv->rx_current++%RX_RING_SIZE;
+ rx_fd = dpriv->rx_fd + cur;
+ if (!(rx_fd->state2 & DataComplete))
+ break;
+ if (rx_fd->state2 & FrameAborted) {
+ hdlc_stats(dev)->rx_over_errors++;
+ rx_fd->state1 |= Hold;
+ rx_fd->state2 = 0x00000000;
+ rx_fd->end = 0xbabeface;
+ } else
+ dscc4_rx_skb(dpriv, dev);
+ } while (1);
+
+ if (debug > 0) {
+ if (dpriv->flags & RdoSet)
+ printk(KERN_DEBUG
+ "%s: no RDO in Rx data\n", DRV_NAME);
+ }
+#ifdef DSCC4_RDO_EXPERIMENTAL_RECOVERY
+ /*
+ * FIXME: must the reset be this violent ?
+ */
+#warning "FIXME: CH0BRDA"
+ writel(dpriv->rx_fd_dma +
+ (dpriv->rx_current%RX_RING_SIZE)*
+ sizeof(struct RxFD), scc_addr + CH0BRDA);
+ writel(MTFi|Rdr|Idr, scc_addr + CH0CFG);
+ if (dscc4_do_action(dev, "RDR") < 0) {
+ printk(KERN_ERR "%s: RDO recovery failed(%s)\n",
+ dev->name, "RDR");
+ goto rdo_end;
+ }
+ writel(MTFi|Idr, scc_addr + CH0CFG);
+ if (dscc4_do_action(dev, "IDR") < 0) {
+ printk(KERN_ERR "%s: RDO recovery failed(%s)\n",
+ dev->name, "IDR");
+ goto rdo_end;
+ }
+ rdo_end:
+#endif
+ scc_patchl(0, RxActivate, dpriv, dev, CCR2);
+ goto try;
+ }
+ if (state & Cd) {
+ printk(KERN_INFO "%s: CD transition\n", dev->name);
+ if (!(state &= ~Cd)) /* DEBUG */
+ goto try;
+ }
+ if (state & Flex) {
+ printk(KERN_DEBUG "%s: Flex. Ttttt...\n", DRV_NAME);
+ if (!(state &= ~Flex))
+ goto try;
+ }
+ }
+}
+
+/*
+ * I had expected the following to work for the first descriptor
+ * (tx_fd->state = 0xc0000000)
+ * - Hold=1 (don't try and branch to the next descripto);
+ * - No=0 (I want an empty data section, i.e. size=0);
+ * - Fe=1 (required by No=0 or we got an Err irq and must reset).
+ * It failed and locked solid. Thus the introduction of a dummy skb.
+ * Problem is acknowledged in errata sheet DS5. Joy :o/
+ */
+struct sk_buff *dscc4_init_dummy_skb(struct dscc4_dev_priv *dpriv)
+{
+ struct sk_buff *skb;
+
+ skb = dev_alloc_skb(DUMMY_SKB_SIZE);
+ if (skb) {
+ int last = dpriv->tx_dirty%TX_RING_SIZE;
+ struct TxFD *tx_fd = dpriv->tx_fd + last;
+
+ skb->len = DUMMY_SKB_SIZE;
+ memcpy(skb->data, version, strlen(version)%DUMMY_SKB_SIZE);
+ tx_fd->state = FrameEnd | TO_STATE_TX(DUMMY_SKB_SIZE);
+ tx_fd->data = pci_map_single(dpriv->pci_priv->pdev, skb->data,
+ DUMMY_SKB_SIZE, PCI_DMA_TODEVICE);
+ dpriv->tx_skbuff[last] = skb;
+ }
+ return skb;
+}
+
+static int dscc4_init_ring(struct net_device *dev)
+{
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+ struct pci_dev *pdev = dpriv->pci_priv->pdev;
+ struct TxFD *tx_fd;
+ struct RxFD *rx_fd;
+ void *ring;
+ int i;
+
+ ring = pci_alloc_consistent(pdev, RX_TOTAL_SIZE, &dpriv->rx_fd_dma);
+ if (!ring)
+ goto err_out;
+ dpriv->rx_fd = rx_fd = (struct RxFD *) ring;
+
+ ring = pci_alloc_consistent(pdev, TX_TOTAL_SIZE, &dpriv->tx_fd_dma);
+ if (!ring)
+ goto err_free_dma_rx;
+ dpriv->tx_fd = tx_fd = (struct TxFD *) ring;
+
+ memset(dpriv->tx_skbuff, 0, sizeof(struct sk_buff *)*TX_RING_SIZE);
+ dpriv->tx_dirty = 0xffffffff;
+ i = dpriv->tx_current = 0;
+ do {
+ tx_fd->state = FrameEnd | TO_STATE_TX(2*DUMMY_SKB_SIZE);
+ tx_fd->complete = 0x00000000;
+ /* FIXME: NULL should be ok - to be tried */
+ tx_fd->data = dpriv->tx_fd_dma;
+ (tx_fd++)->next = (u32)(dpriv->tx_fd_dma +
+ (++i%TX_RING_SIZE)*sizeof(*tx_fd));
+ } while (i < TX_RING_SIZE);
+
+ if (dscc4_init_dummy_skb(dpriv) < 0)
+ goto err_free_dma_tx;
+
+ memset(dpriv->rx_skbuff, 0, sizeof(struct sk_buff *)*RX_RING_SIZE);
+ i = dpriv->rx_dirty = dpriv->rx_current = 0;
+ do {
+ /* size set by the host. Multiple of 4 bytes please */
+ rx_fd->state1 = HiDesc;
+ rx_fd->state2 = 0x00000000;
+ rx_fd->end = 0xbabeface;
+ rx_fd->state1 |= TO_STATE_RX(HDLC_MAX_MRU);
+ // FIXME: return value verifiee mais traitement suspect
+ if (try_get_rx_skb(dpriv, dev) >= 0)
+ dpriv->rx_dirty++;
+ (rx_fd++)->next = (u32)(dpriv->rx_fd_dma +
+ (++i%RX_RING_SIZE)*sizeof(*rx_fd));
+ } while (i < RX_RING_SIZE);
+
+ return 0;
+
+err_free_dma_tx:
+ pci_free_consistent(pdev, TX_TOTAL_SIZE, ring, dpriv->tx_fd_dma);
+err_free_dma_rx:
+ pci_free_consistent(pdev, RX_TOTAL_SIZE, rx_fd, dpriv->rx_fd_dma);
+err_out:
+ return -ENOMEM;
+}
+
+static void __devexit dscc4_remove_one(struct pci_dev *pdev)
+{
+ struct dscc4_pci_priv *ppriv;
+ struct dscc4_dev_priv *root;
+ void __iomem *ioaddr;
+ int i;
+
+ ppriv = pci_get_drvdata(pdev);
+ root = ppriv->root;
+
+ ioaddr = root->base_addr;
+
+ dscc4_pci_reset(pdev, ioaddr);
+
+ free_irq(pdev->irq, root);
+ pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32), ppriv->iqcfg,
+ ppriv->iqcfg_dma);
+ for (i = 0; i < dev_per_card; i++) {
+ struct dscc4_dev_priv *dpriv = root + i;
+
+ dscc4_release_ring(dpriv);
+ pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+ dpriv->iqrx, dpriv->iqrx_dma);
+ pci_free_consistent(pdev, IRQ_RING_SIZE*sizeof(u32),
+ dpriv->iqtx, dpriv->iqtx_dma);
+ }
+
+ dscc4_free1(pdev);
+
+ iounmap(ioaddr);
+
+ pci_release_region(pdev, 1);
+ pci_release_region(pdev, 0);
+
+ pci_disable_device(pdev);
+}
+
+static int dscc4_hdlc_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ struct dscc4_dev_priv *dpriv = dscc4_priv(dev);
+
+ if (encoding != ENCODING_NRZ &&
+ encoding != ENCODING_NRZI &&
+ encoding != ENCODING_FM_MARK &&
+ encoding != ENCODING_FM_SPACE &&
+ encoding != ENCODING_MANCHESTER)
+ return -EINVAL;
+
+ if (parity != PARITY_NONE &&
+ parity != PARITY_CRC16_PR0_CCITT &&
+ parity != PARITY_CRC16_PR1_CCITT &&
+ parity != PARITY_CRC32_PR0_CCITT &&
+ parity != PARITY_CRC32_PR1_CCITT)
+ return -EINVAL;
+
+ dpriv->encoding = encoding;
+ dpriv->parity = parity;
+ return 0;
+}
+
+#ifndef MODULE
+static int __init dscc4_setup(char *str)
+{
+ int *args[] = { &debug, &quartz, NULL }, **p = args;
+
+ while (*p && (get_option(&str, *p) == 2))
+ p++;
+ return 1;
+}
+
+__setup("dscc4.setup=", dscc4_setup);
+#endif
+
+static struct pci_device_id dscc4_pci_tbl[] = {
+ { PCI_VENDOR_ID_SIEMENS, PCI_DEVICE_ID_SIEMENS_DSCC4,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { 0,}
+};
+MODULE_DEVICE_TABLE(pci, dscc4_pci_tbl);
+
+static struct pci_driver dscc4_driver = {
+ .name = DRV_NAME,
+ .id_table = dscc4_pci_tbl,
+ .probe = dscc4_init_one,
+ .remove = __devexit_p(dscc4_remove_one),
+};
+
+static int __init dscc4_init_module(void)
+{
+ return pci_module_init(&dscc4_driver);
+}
+
+static void __exit dscc4_cleanup_module(void)
+{
+ pci_unregister_driver(&dscc4_driver);
+}
+
+module_init(dscc4_init_module);
+module_exit(dscc4_cleanup_module);
diff --git a/drivers/net/wan/farsync.c b/drivers/net/wan/farsync.c
new file mode 100644
index 00000000000..7575b799ce5
--- /dev/null
+++ b/drivers/net/wan/farsync.c
@@ -0,0 +1,2712 @@
+/*
+ * FarSync WAN driver for Linux (2.6.x kernel version)
+ *
+ * Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards
+ *
+ * Copyright (C) 2001-2004 FarSite Communications Ltd.
+ * www.farsite.co.uk
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Author: R.J.Dunlop <bob.dunlop@farsite.co.uk>
+ * Maintainer: Kevin Curtis <kevin.curtis@farsite.co.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/if.h>
+#include <linux/hdlc.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "farsync.h"
+
+/*
+ * Module info
+ */
+MODULE_AUTHOR("R.J.Dunlop <bob.dunlop@farsite.co.uk>");
+MODULE_DESCRIPTION("FarSync T-Series WAN driver. FarSite Communications Ltd.");
+MODULE_LICENSE("GPL");
+
+/* Driver configuration and global parameters
+ * ==========================================
+ */
+
+/* Number of ports (per card) and cards supported
+ */
+#define FST_MAX_PORTS 4
+#define FST_MAX_CARDS 32
+
+/* Default parameters for the link
+ */
+#define FST_TX_QUEUE_LEN 100 /* At 8Mbps a longer queue length is
+ * useful, the syncppp module forces
+ * this down assuming a slower line I
+ * guess.
+ */
+#define FST_TXQ_DEPTH 16 /* This one is for the buffering
+ * of frames on the way down to the card
+ * so that we can keep the card busy
+ * and maximise throughput
+ */
+#define FST_HIGH_WATER_MARK 12 /* Point at which we flow control
+ * network layer */
+#define FST_LOW_WATER_MARK 8 /* Point at which we remove flow
+ * control from network layer */
+#define FST_MAX_MTU 8000 /* Huge but possible */
+#define FST_DEF_MTU 1500 /* Common sane value */
+
+#define FST_TX_TIMEOUT (2*HZ)
+
+#ifdef ARPHRD_RAWHDLC
+#define ARPHRD_MYTYPE ARPHRD_RAWHDLC /* Raw frames */
+#else
+#define ARPHRD_MYTYPE ARPHRD_HDLC /* Cisco-HDLC (keepalives etc) */
+#endif
+
+/*
+ * Modules parameters and associated varaibles
+ */
+int fst_txq_low = FST_LOW_WATER_MARK;
+int fst_txq_high = FST_HIGH_WATER_MARK;
+int fst_max_reads = 7;
+int fst_excluded_cards = 0;
+int fst_excluded_list[FST_MAX_CARDS];
+
+module_param(fst_txq_low, int, 0);
+module_param(fst_txq_high, int, 0);
+module_param(fst_max_reads, int, 0);
+module_param(fst_excluded_cards, int, 0);
+module_param_array(fst_excluded_list, int, NULL, 0);
+
+/* Card shared memory layout
+ * =========================
+ */
+#pragma pack(1)
+
+/* This information is derived in part from the FarSite FarSync Smc.h
+ * file. Unfortunately various name clashes and the non-portability of the
+ * bit field declarations in that file have meant that I have chosen to
+ * recreate the information here.
+ *
+ * The SMC (Shared Memory Configuration) has a version number that is
+ * incremented every time there is a significant change. This number can
+ * be used to check that we have not got out of step with the firmware
+ * contained in the .CDE files.
+ */
+#define SMC_VERSION 24
+
+#define FST_MEMSIZE 0x100000 /* Size of card memory (1Mb) */
+
+#define SMC_BASE 0x00002000L /* Base offset of the shared memory window main
+ * configuration structure */
+#define BFM_BASE 0x00010000L /* Base offset of the shared memory window DMA
+ * buffers */
+
+#define LEN_TX_BUFFER 8192 /* Size of packet buffers */
+#define LEN_RX_BUFFER 8192
+
+#define LEN_SMALL_TX_BUFFER 256 /* Size of obsolete buffs used for DOS diags */
+#define LEN_SMALL_RX_BUFFER 256
+
+#define NUM_TX_BUFFER 2 /* Must be power of 2. Fixed by firmware */
+#define NUM_RX_BUFFER 8
+
+/* Interrupt retry time in milliseconds */
+#define INT_RETRY_TIME 2
+
+/* The Am186CH/CC processors support a SmartDMA mode using circular pools
+ * of buffer descriptors. The structure is almost identical to that used
+ * in the LANCE Ethernet controllers. Details available as PDF from the
+ * AMD web site: http://www.amd.com/products/epd/processors/\
+ * 2.16bitcont/3.am186cxfa/a21914/21914.pdf
+ */
+struct txdesc { /* Transmit descriptor */
+ volatile u16 ladr; /* Low order address of packet. This is a
+ * linear address in the Am186 memory space
+ */
+ volatile u8 hadr; /* High order address. Low 4 bits only, high 4
+ * bits must be zero
+ */
+ volatile u8 bits; /* Status and config */
+ volatile u16 bcnt; /* 2s complement of packet size in low 15 bits.
+ * Transmit terminal count interrupt enable in
+ * top bit.
+ */
+ u16 unused; /* Not used in Tx */
+};
+
+struct rxdesc { /* Receive descriptor */
+ volatile u16 ladr; /* Low order address of packet */
+ volatile u8 hadr; /* High order address */
+ volatile u8 bits; /* Status and config */
+ volatile u16 bcnt; /* 2s complement of buffer size in low 15 bits.
+ * Receive terminal count interrupt enable in
+ * top bit.
+ */
+ volatile u16 mcnt; /* Message byte count (15 bits) */
+};
+
+/* Convert a length into the 15 bit 2's complement */
+/* #define cnv_bcnt(len) (( ~(len) + 1 ) & 0x7FFF ) */
+/* Since we need to set the high bit to enable the completion interrupt this
+ * can be made a lot simpler
+ */
+#define cnv_bcnt(len) (-(len))
+
+/* Status and config bits for the above */
+#define DMA_OWN 0x80 /* SmartDMA owns the descriptor */
+#define TX_STP 0x02 /* Tx: start of packet */
+#define TX_ENP 0x01 /* Tx: end of packet */
+#define RX_ERR 0x40 /* Rx: error (OR of next 4 bits) */
+#define RX_FRAM 0x20 /* Rx: framing error */
+#define RX_OFLO 0x10 /* Rx: overflow error */
+#define RX_CRC 0x08 /* Rx: CRC error */
+#define RX_HBUF 0x04 /* Rx: buffer error */
+#define RX_STP 0x02 /* Rx: start of packet */
+#define RX_ENP 0x01 /* Rx: end of packet */
+
+/* Interrupts from the card are caused by various events which are presented
+ * in a circular buffer as several events may be processed on one physical int
+ */
+#define MAX_CIRBUFF 32
+
+struct cirbuff {
+ u8 rdindex; /* read, then increment and wrap */
+ u8 wrindex; /* write, then increment and wrap */
+ u8 evntbuff[MAX_CIRBUFF];
+};
+
+/* Interrupt event codes.
+ * Where appropriate the two low order bits indicate the port number
+ */
+#define CTLA_CHG 0x18 /* Control signal changed */
+#define CTLB_CHG 0x19
+#define CTLC_CHG 0x1A
+#define CTLD_CHG 0x1B
+
+#define INIT_CPLT 0x20 /* Initialisation complete */
+#define INIT_FAIL 0x21 /* Initialisation failed */
+
+#define ABTA_SENT 0x24 /* Abort sent */
+#define ABTB_SENT 0x25
+#define ABTC_SENT 0x26
+#define ABTD_SENT 0x27
+
+#define TXA_UNDF 0x28 /* Transmission underflow */
+#define TXB_UNDF 0x29
+#define TXC_UNDF 0x2A
+#define TXD_UNDF 0x2B
+
+#define F56_INT 0x2C
+#define M32_INT 0x2D
+
+#define TE1_ALMA 0x30
+
+/* Port physical configuration. See farsync.h for field values */
+struct port_cfg {
+ u16 lineInterface; /* Physical interface type */
+ u8 x25op; /* Unused at present */
+ u8 internalClock; /* 1 => internal clock, 0 => external */
+ u8 transparentMode; /* 1 => on, 0 => off */
+ u8 invertClock; /* 0 => normal, 1 => inverted */
+ u8 padBytes[6]; /* Padding */
+ u32 lineSpeed; /* Speed in bps */
+};
+
+/* TE1 port physical configuration */
+struct su_config {
+ u32 dataRate;
+ u8 clocking;
+ u8 framing;
+ u8 structure;
+ u8 interface;
+ u8 coding;
+ u8 lineBuildOut;
+ u8 equalizer;
+ u8 transparentMode;
+ u8 loopMode;
+ u8 range;
+ u8 txBufferMode;
+ u8 rxBufferMode;
+ u8 startingSlot;
+ u8 losThreshold;
+ u8 enableIdleCode;
+ u8 idleCode;
+ u8 spare[44];
+};
+
+/* TE1 Status */
+struct su_status {
+ u32 receiveBufferDelay;
+ u32 framingErrorCount;
+ u32 codeViolationCount;
+ u32 crcErrorCount;
+ u32 lineAttenuation;
+ u8 portStarted;
+ u8 lossOfSignal;
+ u8 receiveRemoteAlarm;
+ u8 alarmIndicationSignal;
+ u8 spare[40];
+};
+
+/* Finally sling all the above together into the shared memory structure.
+ * Sorry it's a hodge podge of arrays, structures and unused bits, it's been
+ * evolving under NT for some time so I guess we're stuck with it.
+ * The structure starts at offset SMC_BASE.
+ * See farsync.h for some field values.
+ */
+struct fst_shared {
+ /* DMA descriptor rings */
+ struct rxdesc rxDescrRing[FST_MAX_PORTS][NUM_RX_BUFFER];
+ struct txdesc txDescrRing[FST_MAX_PORTS][NUM_TX_BUFFER];
+
+ /* Obsolete small buffers */
+ u8 smallRxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_SMALL_RX_BUFFER];
+ u8 smallTxBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_SMALL_TX_BUFFER];
+
+ u8 taskStatus; /* 0x00 => initialising, 0x01 => running,
+ * 0xFF => halted
+ */
+
+ u8 interruptHandshake; /* Set to 0x01 by adapter to signal interrupt,
+ * set to 0xEE by host to acknowledge interrupt
+ */
+
+ u16 smcVersion; /* Must match SMC_VERSION */
+
+ u32 smcFirmwareVersion; /* 0xIIVVRRBB where II = product ID, VV = major
+ * version, RR = revision and BB = build
+ */
+
+ u16 txa_done; /* Obsolete completion flags */
+ u16 rxa_done;
+ u16 txb_done;
+ u16 rxb_done;
+ u16 txc_done;
+ u16 rxc_done;
+ u16 txd_done;
+ u16 rxd_done;
+
+ u16 mailbox[4]; /* Diagnostics mailbox. Not used */
+
+ struct cirbuff interruptEvent; /* interrupt causes */
+
+ u32 v24IpSts[FST_MAX_PORTS]; /* V.24 control input status */
+ u32 v24OpSts[FST_MAX_PORTS]; /* V.24 control output status */
+
+ struct port_cfg portConfig[FST_MAX_PORTS];
+
+ u16 clockStatus[FST_MAX_PORTS]; /* lsb: 0=> present, 1=> absent */
+
+ u16 cableStatus; /* lsb: 0=> present, 1=> absent */
+
+ u16 txDescrIndex[FST_MAX_PORTS]; /* transmit descriptor ring index */
+ u16 rxDescrIndex[FST_MAX_PORTS]; /* receive descriptor ring index */
+
+ u16 portMailbox[FST_MAX_PORTS][2]; /* command, modifier */
+ u16 cardMailbox[4]; /* Not used */
+
+ /* Number of times the card thinks the host has
+ * missed an interrupt by not acknowledging
+ * within 2mS (I guess NT has problems)
+ */
+ u32 interruptRetryCount;
+
+ /* Driver private data used as an ID. We'll not
+ * use this as I'd rather keep such things
+ * in main memory rather than on the PCI bus
+ */
+ u32 portHandle[FST_MAX_PORTS];
+
+ /* Count of Tx underflows for stats */
+ u32 transmitBufferUnderflow[FST_MAX_PORTS];
+
+ /* Debounced V.24 control input status */
+ u32 v24DebouncedSts[FST_MAX_PORTS];
+
+ /* Adapter debounce timers. Don't touch */
+ u32 ctsTimer[FST_MAX_PORTS];
+ u32 ctsTimerRun[FST_MAX_PORTS];
+ u32 dcdTimer[FST_MAX_PORTS];
+ u32 dcdTimerRun[FST_MAX_PORTS];
+
+ u32 numberOfPorts; /* Number of ports detected at startup */
+
+ u16 _reserved[64];
+
+ u16 cardMode; /* Bit-mask to enable features:
+ * Bit 0: 1 enables LED identify mode
+ */
+
+ u16 portScheduleOffset;
+
+ struct su_config suConfig; /* TE1 Bits */
+ struct su_status suStatus;
+
+ u32 endOfSmcSignature; /* endOfSmcSignature MUST be the last member of
+ * the structure and marks the end of shared
+ * memory. Adapter code initializes it as
+ * END_SIG.
+ */
+};
+
+/* endOfSmcSignature value */
+#define END_SIG 0x12345678
+
+/* Mailbox values. (portMailbox) */
+#define NOP 0 /* No operation */
+#define ACK 1 /* Positive acknowledgement to PC driver */
+#define NAK 2 /* Negative acknowledgement to PC driver */
+#define STARTPORT 3 /* Start an HDLC port */
+#define STOPPORT 4 /* Stop an HDLC port */
+#define ABORTTX 5 /* Abort the transmitter for a port */
+#define SETV24O 6 /* Set V24 outputs */
+
+/* PLX Chip Register Offsets */
+#define CNTRL_9052 0x50 /* Control Register */
+#define CNTRL_9054 0x6c /* Control Register */
+
+#define INTCSR_9052 0x4c /* Interrupt control/status register */
+#define INTCSR_9054 0x68 /* Interrupt control/status register */
+
+/* 9054 DMA Registers */
+/*
+ * Note that we will be using DMA Channel 0 for copying rx data
+ * and Channel 1 for copying tx data
+ */
+#define DMAMODE0 0x80
+#define DMAPADR0 0x84
+#define DMALADR0 0x88
+#define DMASIZ0 0x8c
+#define DMADPR0 0x90
+#define DMAMODE1 0x94
+#define DMAPADR1 0x98
+#define DMALADR1 0x9c
+#define DMASIZ1 0xa0
+#define DMADPR1 0xa4
+#define DMACSR0 0xa8
+#define DMACSR1 0xa9
+#define DMAARB 0xac
+#define DMATHR 0xb0
+#define DMADAC0 0xb4
+#define DMADAC1 0xb8
+#define DMAMARBR 0xac
+
+#define FST_MIN_DMA_LEN 64
+#define FST_RX_DMA_INT 0x01
+#define FST_TX_DMA_INT 0x02
+#define FST_CARD_INT 0x04
+
+/* Larger buffers are positioned in memory at offset BFM_BASE */
+struct buf_window {
+ u8 txBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_TX_BUFFER];
+ u8 rxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_RX_BUFFER];
+};
+
+/* Calculate offset of a buffer object within the shared memory window */
+#define BUF_OFFSET(X) (BFM_BASE + offsetof(struct buf_window, X))
+
+#pragma pack()
+
+/* Device driver private information
+ * =================================
+ */
+/* Per port (line or channel) information
+ */
+struct fst_port_info {
+ struct net_device *dev; /* Device struct - must be first */
+ struct fst_card_info *card; /* Card we're associated with */
+ int index; /* Port index on the card */
+ int hwif; /* Line hardware (lineInterface copy) */
+ int run; /* Port is running */
+ int mode; /* Normal or FarSync raw */
+ int rxpos; /* Next Rx buffer to use */
+ int txpos; /* Next Tx buffer to use */
+ int txipos; /* Next Tx buffer to check for free */
+ int start; /* Indication of start/stop to network */
+ /*
+ * A sixteen entry transmit queue
+ */
+ int txqs; /* index to get next buffer to tx */
+ int txqe; /* index to queue next packet */
+ struct sk_buff *txq[FST_TXQ_DEPTH]; /* The queue */
+ int rxqdepth;
+};
+
+/* Per card information
+ */
+struct fst_card_info {
+ char __iomem *mem; /* Card memory mapped to kernel space */
+ char __iomem *ctlmem; /* Control memory for PCI cards */
+ unsigned int phys_mem; /* Physical memory window address */
+ unsigned int phys_ctlmem; /* Physical control memory address */
+ unsigned int irq; /* Interrupt request line number */
+ unsigned int nports; /* Number of serial ports */
+ unsigned int type; /* Type index of card */
+ unsigned int state; /* State of card */
+ spinlock_t card_lock; /* Lock for SMP access */
+ unsigned short pci_conf; /* PCI card config in I/O space */
+ /* Per port info */
+ struct fst_port_info ports[FST_MAX_PORTS];
+ struct pci_dev *device; /* Information about the pci device */
+ int card_no; /* Inst of the card on the system */
+ int family; /* TxP or TxU */
+ int dmarx_in_progress;
+ int dmatx_in_progress;
+ unsigned long int_count;
+ unsigned long int_time_ave;
+ void *rx_dma_handle_host;
+ dma_addr_t rx_dma_handle_card;
+ void *tx_dma_handle_host;
+ dma_addr_t tx_dma_handle_card;
+ struct sk_buff *dma_skb_rx;
+ struct fst_port_info *dma_port_rx;
+ struct fst_port_info *dma_port_tx;
+ int dma_len_rx;
+ int dma_len_tx;
+ int dma_txpos;
+ int dma_rxpos;
+};
+
+/* Convert an HDLC device pointer into a port info pointer and similar */
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+#define port_to_dev(P) ((P)->dev)
+
+
+/*
+ * Shared memory window access macros
+ *
+ * We have a nice memory based structure above, which could be directly
+ * mapped on i386 but might not work on other architectures unless we use
+ * the readb,w,l and writeb,w,l macros. Unfortunately these macros take
+ * physical offsets so we have to convert. The only saving grace is that
+ * this should all collapse back to a simple indirection eventually.
+ */
+#define WIN_OFFSET(X) ((long)&(((struct fst_shared *)SMC_BASE)->X))
+
+#define FST_RDB(C,E) readb ((C)->mem + WIN_OFFSET(E))
+#define FST_RDW(C,E) readw ((C)->mem + WIN_OFFSET(E))
+#define FST_RDL(C,E) readl ((C)->mem + WIN_OFFSET(E))
+
+#define FST_WRB(C,E,B) writeb ((B), (C)->mem + WIN_OFFSET(E))
+#define FST_WRW(C,E,W) writew ((W), (C)->mem + WIN_OFFSET(E))
+#define FST_WRL(C,E,L) writel ((L), (C)->mem + WIN_OFFSET(E))
+
+/*
+ * Debug support
+ */
+#if FST_DEBUG
+
+static int fst_debug_mask = { FST_DEBUG };
+
+/* Most common debug activity is to print something if the corresponding bit
+ * is set in the debug mask. Note: this uses a non-ANSI extension in GCC to
+ * support variable numbers of macro parameters. The inverted if prevents us
+ * eating someone else's else clause.
+ */
+#define dbg(F,fmt,A...) if ( ! ( fst_debug_mask & (F))) \
+ ; \
+ else \
+ printk ( KERN_DEBUG FST_NAME ": " fmt, ## A )
+
+#else
+#define dbg(X...) /* NOP */
+#endif
+
+/* Printing short cuts
+ */
+#define printk_err(fmt,A...) printk ( KERN_ERR FST_NAME ": " fmt, ## A )
+#define printk_warn(fmt,A...) printk ( KERN_WARNING FST_NAME ": " fmt, ## A )
+#define printk_info(fmt,A...) printk ( KERN_INFO FST_NAME ": " fmt, ## A )
+
+/*
+ * PCI ID lookup table
+ */
+static struct pci_device_id fst_pci_dev_id[] __devinitdata = {
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T2P, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_T2P},
+
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T4P, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_T4P},
+
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T1U, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_T1U},
+
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T2U, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_T2U},
+
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_T4U, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_T4U},
+
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_TE1, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_TE1},
+
+ {PCI_VENDOR_ID_FARSITE, PCI_DEVICE_ID_FARSITE_TE1C, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, FST_TYPE_TE1},
+ {0,} /* End */
+};
+
+MODULE_DEVICE_TABLE(pci, fst_pci_dev_id);
+
+/*
+ * Device Driver Work Queues
+ *
+ * So that we don't spend too much time processing events in the
+ * Interrupt Service routine, we will declare a work queue per Card
+ * and make the ISR schedule a task in the queue for later execution.
+ * In the 2.4 Kernel we used to use the immediate queue for BH's
+ * Now that they are gone, tasklets seem to be much better than work
+ * queues.
+ */
+
+static void do_bottom_half_tx(struct fst_card_info *card);
+static void do_bottom_half_rx(struct fst_card_info *card);
+static void fst_process_tx_work_q(unsigned long work_q);
+static void fst_process_int_work_q(unsigned long work_q);
+
+DECLARE_TASKLET(fst_tx_task, fst_process_tx_work_q, 0);
+DECLARE_TASKLET(fst_int_task, fst_process_int_work_q, 0);
+
+struct fst_card_info *fst_card_array[FST_MAX_CARDS];
+spinlock_t fst_work_q_lock;
+u64 fst_work_txq;
+u64 fst_work_intq;
+
+static void
+fst_q_work_item(u64 * queue, int card_index)
+{
+ unsigned long flags;
+ u64 mask;
+
+ /*
+ * Grab the queue exclusively
+ */
+ spin_lock_irqsave(&fst_work_q_lock, flags);
+
+ /*
+ * Making an entry in the queue is simply a matter of setting
+ * a bit for the card indicating that there is work to do in the
+ * bottom half for the card. Note the limitation of 64 cards.
+ * That ought to be enough
+ */
+ mask = 1 << card_index;
+ *queue |= mask;
+ spin_unlock_irqrestore(&fst_work_q_lock, flags);
+}
+
+static void
+fst_process_tx_work_q(unsigned long /*void **/work_q)
+{
+ unsigned long flags;
+ u64 work_txq;
+ int i;
+
+ /*
+ * Grab the queue exclusively
+ */
+ dbg(DBG_TX, "fst_process_tx_work_q\n");
+ spin_lock_irqsave(&fst_work_q_lock, flags);
+ work_txq = fst_work_txq;
+ fst_work_txq = 0;
+ spin_unlock_irqrestore(&fst_work_q_lock, flags);
+
+ /*
+ * Call the bottom half for each card with work waiting
+ */
+ for (i = 0; i < FST_MAX_CARDS; i++) {
+ if (work_txq & 0x01) {
+ if (fst_card_array[i] != NULL) {
+ dbg(DBG_TX, "Calling tx bh for card %d\n", i);
+ do_bottom_half_tx(fst_card_array[i]);
+ }
+ }
+ work_txq = work_txq >> 1;
+ }
+}
+
+static void
+fst_process_int_work_q(unsigned long /*void **/work_q)
+{
+ unsigned long flags;
+ u64 work_intq;
+ int i;
+
+ /*
+ * Grab the queue exclusively
+ */
+ dbg(DBG_INTR, "fst_process_int_work_q\n");
+ spin_lock_irqsave(&fst_work_q_lock, flags);
+ work_intq = fst_work_intq;
+ fst_work_intq = 0;
+ spin_unlock_irqrestore(&fst_work_q_lock, flags);
+
+ /*
+ * Call the bottom half for each card with work waiting
+ */
+ for (i = 0; i < FST_MAX_CARDS; i++) {
+ if (work_intq & 0x01) {
+ if (fst_card_array[i] != NULL) {
+ dbg(DBG_INTR,
+ "Calling rx & tx bh for card %d\n", i);
+ do_bottom_half_rx(fst_card_array[i]);
+ do_bottom_half_tx(fst_card_array[i]);
+ }
+ }
+ work_intq = work_intq >> 1;
+ }
+}
+
+/* Card control functions
+ * ======================
+ */
+/* Place the processor in reset state
+ *
+ * Used to be a simple write to card control space but a glitch in the latest
+ * AMD Am186CH processor means that we now have to do it by asserting and de-
+ * asserting the PLX chip PCI Adapter Software Reset. Bit 30 in CNTRL register
+ * at offset 9052_CNTRL. Note the updates for the TXU.
+ */
+static inline void
+fst_cpureset(struct fst_card_info *card)
+{
+ unsigned char interrupt_line_register;
+ unsigned long j = jiffies + 1;
+ unsigned int regval;
+
+ if (card->family == FST_FAMILY_TXU) {
+ if (pci_read_config_byte
+ (card->device, PCI_INTERRUPT_LINE, &interrupt_line_register)) {
+ dbg(DBG_ASS,
+ "Error in reading interrupt line register\n");
+ }
+ /*
+ * Assert PLX software reset and Am186 hardware reset
+ * and then deassert the PLX software reset but 186 still in reset
+ */
+ outw(0x440f, card->pci_conf + CNTRL_9054 + 2);
+ outw(0x040f, card->pci_conf + CNTRL_9054 + 2);
+ /*
+ * We are delaying here to allow the 9054 to reset itself
+ */
+ j = jiffies + 1;
+ while (jiffies < j)
+ /* Do nothing */ ;
+ outw(0x240f, card->pci_conf + CNTRL_9054 + 2);
+ /*
+ * We are delaying here to allow the 9054 to reload its eeprom
+ */
+ j = jiffies + 1;
+ while (jiffies < j)
+ /* Do nothing */ ;
+ outw(0x040f, card->pci_conf + CNTRL_9054 + 2);
+
+ if (pci_write_config_byte
+ (card->device, PCI_INTERRUPT_LINE, interrupt_line_register)) {
+ dbg(DBG_ASS,
+ "Error in writing interrupt line register\n");
+ }
+
+ } else {
+ regval = inl(card->pci_conf + CNTRL_9052);
+
+ outl(regval | 0x40000000, card->pci_conf + CNTRL_9052);
+ outl(regval & ~0x40000000, card->pci_conf + CNTRL_9052);
+ }
+}
+
+/* Release the processor from reset
+ */
+static inline void
+fst_cpurelease(struct fst_card_info *card)
+{
+ if (card->family == FST_FAMILY_TXU) {
+ /*
+ * Force posted writes to complete
+ */
+ (void) readb(card->mem);
+
+ /*
+ * Release LRESET DO = 1
+ * Then release Local Hold, DO = 1
+ */
+ outw(0x040e, card->pci_conf + CNTRL_9054 + 2);
+ outw(0x040f, card->pci_conf + CNTRL_9054 + 2);
+ } else {
+ (void) readb(card->ctlmem);
+ }
+}
+
+/* Clear the cards interrupt flag
+ */
+static inline void
+fst_clear_intr(struct fst_card_info *card)
+{
+ if (card->family == FST_FAMILY_TXU) {
+ (void) readb(card->ctlmem);
+ } else {
+ /* Poke the appropriate PLX chip register (same as enabling interrupts)
+ */
+ outw(0x0543, card->pci_conf + INTCSR_9052);
+ }
+}
+
+/* Enable card interrupts
+ */
+static inline void
+fst_enable_intr(struct fst_card_info *card)
+{
+ if (card->family == FST_FAMILY_TXU) {
+ outl(0x0f0c0900, card->pci_conf + INTCSR_9054);
+ } else {
+ outw(0x0543, card->pci_conf + INTCSR_9052);
+ }
+}
+
+/* Disable card interrupts
+ */
+static inline void
+fst_disable_intr(struct fst_card_info *card)
+{
+ if (card->family == FST_FAMILY_TXU) {
+ outl(0x00000000, card->pci_conf + INTCSR_9054);
+ } else {
+ outw(0x0000, card->pci_conf + INTCSR_9052);
+ }
+}
+
+/* Process the result of trying to pass a received frame up the stack
+ */
+static void
+fst_process_rx_status(int rx_status, char *name)
+{
+ switch (rx_status) {
+ case NET_RX_SUCCESS:
+ {
+ /*
+ * Nothing to do here
+ */
+ break;
+ }
+
+ case NET_RX_CN_LOW:
+ {
+ dbg(DBG_ASS, "%s: Receive Low Congestion\n", name);
+ break;
+ }
+
+ case NET_RX_CN_MOD:
+ {
+ dbg(DBG_ASS, "%s: Receive Moderate Congestion\n", name);
+ break;
+ }
+
+ case NET_RX_CN_HIGH:
+ {
+ dbg(DBG_ASS, "%s: Receive High Congestion\n", name);
+ break;
+ }
+
+ case NET_RX_DROP:
+ {
+ dbg(DBG_ASS, "%s: Received packet dropped\n", name);
+ break;
+ }
+ }
+}
+
+/* Initilaise DMA for PLX 9054
+ */
+static inline void
+fst_init_dma(struct fst_card_info *card)
+{
+ /*
+ * This is only required for the PLX 9054
+ */
+ if (card->family == FST_FAMILY_TXU) {
+ pci_set_master(card->device);
+ outl(0x00020441, card->pci_conf + DMAMODE0);
+ outl(0x00020441, card->pci_conf + DMAMODE1);
+ outl(0x0, card->pci_conf + DMATHR);
+ }
+}
+
+/* Tx dma complete interrupt
+ */
+static void
+fst_tx_dma_complete(struct fst_card_info *card, struct fst_port_info *port,
+ int len, int txpos)
+{
+ struct net_device *dev = port_to_dev(port);
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ /*
+ * Everything is now set, just tell the card to go
+ */
+ dbg(DBG_TX, "fst_tx_dma_complete\n");
+ FST_WRB(card, txDescrRing[port->index][txpos].bits,
+ DMA_OWN | TX_STP | TX_ENP);
+ stats->tx_packets++;
+ stats->tx_bytes += len;
+ dev->trans_start = jiffies;
+}
+
+/*
+ * Mark it for our own raw sockets interface
+ */
+static unsigned short farsync_type_trans(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ skb->dev = dev;
+ skb->mac.raw = skb->data;
+ skb->pkt_type = PACKET_HOST;
+ return htons(ETH_P_CUST);
+}
+
+/* Rx dma complete interrupt
+ */
+static void
+fst_rx_dma_complete(struct fst_card_info *card, struct fst_port_info *port,
+ int len, struct sk_buff *skb, int rxp)
+{
+ struct net_device *dev = port_to_dev(port);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ int pi;
+ int rx_status;
+
+ dbg(DBG_TX, "fst_rx_dma_complete\n");
+ pi = port->index;
+ memcpy(skb_put(skb, len), card->rx_dma_handle_host, len);
+
+ /* Reset buffer descriptor */
+ FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+ /* Update stats */
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+
+ /* Push upstream */
+ dbg(DBG_RX, "Pushing the frame up the stack\n");
+ if (port->mode == FST_RAW)
+ skb->protocol = farsync_type_trans(skb, dev);
+ else
+ skb->protocol = hdlc_type_trans(skb, dev);
+ rx_status = netif_rx(skb);
+ fst_process_rx_status(rx_status, port_to_dev(port)->name);
+ if (rx_status == NET_RX_DROP)
+ stats->rx_dropped++;
+ dev->last_rx = jiffies;
+}
+
+/*
+ * Receive a frame through the DMA
+ */
+static inline void
+fst_rx_dma(struct fst_card_info *card, unsigned char *skb,
+ unsigned char *mem, int len)
+{
+ /*
+ * This routine will setup the DMA and start it
+ */
+
+ dbg(DBG_RX, "In fst_rx_dma %p %p %d\n", skb, mem, len);
+ if (card->dmarx_in_progress) {
+ dbg(DBG_ASS, "In fst_rx_dma while dma in progress\n");
+ }
+
+ outl((unsigned long) skb, card->pci_conf + DMAPADR0); /* Copy to here */
+ outl((unsigned long) mem, card->pci_conf + DMALADR0); /* from here */
+ outl(len, card->pci_conf + DMASIZ0); /* for this length */
+ outl(0x00000000c, card->pci_conf + DMADPR0); /* In this direction */
+
+ /*
+ * We use the dmarx_in_progress flag to flag the channel as busy
+ */
+ card->dmarx_in_progress = 1;
+ outb(0x03, card->pci_conf + DMACSR0); /* Start the transfer */
+}
+
+/*
+ * Send a frame through the DMA
+ */
+static inline void
+fst_tx_dma(struct fst_card_info *card, unsigned char *skb,
+ unsigned char *mem, int len)
+{
+ /*
+ * This routine will setup the DMA and start it.
+ */
+
+ dbg(DBG_TX, "In fst_tx_dma %p %p %d\n", skb, mem, len);
+ if (card->dmatx_in_progress) {
+ dbg(DBG_ASS, "In fst_tx_dma while dma in progress\n");
+ }
+
+ outl((unsigned long) skb, card->pci_conf + DMAPADR1); /* Copy from here */
+ outl((unsigned long) mem, card->pci_conf + DMALADR1); /* to here */
+ outl(len, card->pci_conf + DMASIZ1); /* for this length */
+ outl(0x000000004, card->pci_conf + DMADPR1); /* In this direction */
+
+ /*
+ * We use the dmatx_in_progress to flag the channel as busy
+ */
+ card->dmatx_in_progress = 1;
+ outb(0x03, card->pci_conf + DMACSR1); /* Start the transfer */
+}
+
+/* Issue a Mailbox command for a port.
+ * Note we issue them on a fire and forget basis, not expecting to see an
+ * error and not waiting for completion.
+ */
+static void
+fst_issue_cmd(struct fst_port_info *port, unsigned short cmd)
+{
+ struct fst_card_info *card;
+ unsigned short mbval;
+ unsigned long flags;
+ int safety;
+
+ card = port->card;
+ spin_lock_irqsave(&card->card_lock, flags);
+ mbval = FST_RDW(card, portMailbox[port->index][0]);
+
+ safety = 0;
+ /* Wait for any previous command to complete */
+ while (mbval > NAK) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ schedule_timeout(1);
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ if (++safety > 2000) {
+ printk_err("Mailbox safety timeout\n");
+ break;
+ }
+
+ mbval = FST_RDW(card, portMailbox[port->index][0]);
+ }
+ if (safety > 0) {
+ dbg(DBG_CMD, "Mailbox clear after %d jiffies\n", safety);
+ }
+ if (mbval == NAK) {
+ dbg(DBG_CMD, "issue_cmd: previous command was NAK'd\n");
+ }
+
+ FST_WRW(card, portMailbox[port->index][0], cmd);
+
+ if (cmd == ABORTTX || cmd == STARTPORT) {
+ port->txpos = 0;
+ port->txipos = 0;
+ port->start = 0;
+ }
+
+ spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/* Port output signals control
+ */
+static inline void
+fst_op_raise(struct fst_port_info *port, unsigned int outputs)
+{
+ outputs |= FST_RDL(port->card, v24OpSts[port->index]);
+ FST_WRL(port->card, v24OpSts[port->index], outputs);
+
+ if (port->run)
+ fst_issue_cmd(port, SETV24O);
+}
+
+static inline void
+fst_op_lower(struct fst_port_info *port, unsigned int outputs)
+{
+ outputs = ~outputs & FST_RDL(port->card, v24OpSts[port->index]);
+ FST_WRL(port->card, v24OpSts[port->index], outputs);
+
+ if (port->run)
+ fst_issue_cmd(port, SETV24O);
+}
+
+/*
+ * Setup port Rx buffers
+ */
+static void
+fst_rx_config(struct fst_port_info *port)
+{
+ int i;
+ int pi;
+ unsigned int offset;
+ unsigned long flags;
+ struct fst_card_info *card;
+
+ pi = port->index;
+ card = port->card;
+ spin_lock_irqsave(&card->card_lock, flags);
+ for (i = 0; i < NUM_RX_BUFFER; i++) {
+ offset = BUF_OFFSET(rxBuffer[pi][i][0]);
+
+ FST_WRW(card, rxDescrRing[pi][i].ladr, (u16) offset);
+ FST_WRB(card, rxDescrRing[pi][i].hadr, (u8) (offset >> 16));
+ FST_WRW(card, rxDescrRing[pi][i].bcnt, cnv_bcnt(LEN_RX_BUFFER));
+ FST_WRW(card, rxDescrRing[pi][i].mcnt, LEN_RX_BUFFER);
+ FST_WRB(card, rxDescrRing[pi][i].bits, DMA_OWN);
+ }
+ port->rxpos = 0;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/*
+ * Setup port Tx buffers
+ */
+static void
+fst_tx_config(struct fst_port_info *port)
+{
+ int i;
+ int pi;
+ unsigned int offset;
+ unsigned long flags;
+ struct fst_card_info *card;
+
+ pi = port->index;
+ card = port->card;
+ spin_lock_irqsave(&card->card_lock, flags);
+ for (i = 0; i < NUM_TX_BUFFER; i++) {
+ offset = BUF_OFFSET(txBuffer[pi][i][0]);
+
+ FST_WRW(card, txDescrRing[pi][i].ladr, (u16) offset);
+ FST_WRB(card, txDescrRing[pi][i].hadr, (u8) (offset >> 16));
+ FST_WRW(card, txDescrRing[pi][i].bcnt, 0);
+ FST_WRB(card, txDescrRing[pi][i].bits, 0);
+ }
+ port->txpos = 0;
+ port->txipos = 0;
+ port->start = 0;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/* TE1 Alarm change interrupt event
+ */
+static void
+fst_intr_te1_alarm(struct fst_card_info *card, struct fst_port_info *port)
+{
+ u8 los;
+ u8 rra;
+ u8 ais;
+
+ los = FST_RDB(card, suStatus.lossOfSignal);
+ rra = FST_RDB(card, suStatus.receiveRemoteAlarm);
+ ais = FST_RDB(card, suStatus.alarmIndicationSignal);
+
+ if (los) {
+ /*
+ * Lost the link
+ */
+ if (netif_carrier_ok(port_to_dev(port))) {
+ dbg(DBG_INTR, "Net carrier off\n");
+ netif_carrier_off(port_to_dev(port));
+ }
+ } else {
+ /*
+ * Link available
+ */
+ if (!netif_carrier_ok(port_to_dev(port))) {
+ dbg(DBG_INTR, "Net carrier on\n");
+ netif_carrier_on(port_to_dev(port));
+ }
+ }
+
+ if (los)
+ dbg(DBG_INTR, "Assert LOS Alarm\n");
+ else
+ dbg(DBG_INTR, "De-assert LOS Alarm\n");
+ if (rra)
+ dbg(DBG_INTR, "Assert RRA Alarm\n");
+ else
+ dbg(DBG_INTR, "De-assert RRA Alarm\n");
+
+ if (ais)
+ dbg(DBG_INTR, "Assert AIS Alarm\n");
+ else
+ dbg(DBG_INTR, "De-assert AIS Alarm\n");
+}
+
+/* Control signal change interrupt event
+ */
+static void
+fst_intr_ctlchg(struct fst_card_info *card, struct fst_port_info *port)
+{
+ int signals;
+
+ signals = FST_RDL(card, v24DebouncedSts[port->index]);
+
+ if (signals & (((port->hwif == X21) || (port->hwif == X21D))
+ ? IPSTS_INDICATE : IPSTS_DCD)) {
+ if (!netif_carrier_ok(port_to_dev(port))) {
+ dbg(DBG_INTR, "DCD active\n");
+ netif_carrier_on(port_to_dev(port));
+ }
+ } else {
+ if (netif_carrier_ok(port_to_dev(port))) {
+ dbg(DBG_INTR, "DCD lost\n");
+ netif_carrier_off(port_to_dev(port));
+ }
+ }
+}
+
+/* Log Rx Errors
+ */
+static void
+fst_log_rx_error(struct fst_card_info *card, struct fst_port_info *port,
+ unsigned char dmabits, int rxp, unsigned short len)
+{
+ struct net_device *dev = port_to_dev(port);
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ /*
+ * Increment the appropriate error counter
+ */
+ stats->rx_errors++;
+ if (dmabits & RX_OFLO) {
+ stats->rx_fifo_errors++;
+ dbg(DBG_ASS, "Rx fifo error on card %d port %d buffer %d\n",
+ card->card_no, port->index, rxp);
+ }
+ if (dmabits & RX_CRC) {
+ stats->rx_crc_errors++;
+ dbg(DBG_ASS, "Rx crc error on card %d port %d\n",
+ card->card_no, port->index);
+ }
+ if (dmabits & RX_FRAM) {
+ stats->rx_frame_errors++;
+ dbg(DBG_ASS, "Rx frame error on card %d port %d\n",
+ card->card_no, port->index);
+ }
+ if (dmabits == (RX_STP | RX_ENP)) {
+ stats->rx_length_errors++;
+ dbg(DBG_ASS, "Rx length error (%d) on card %d port %d\n",
+ len, card->card_no, port->index);
+ }
+}
+
+/* Rx Error Recovery
+ */
+static void
+fst_recover_rx_error(struct fst_card_info *card, struct fst_port_info *port,
+ unsigned char dmabits, int rxp, unsigned short len)
+{
+ int i;
+ int pi;
+
+ pi = port->index;
+ /*
+ * Discard buffer descriptors until we see the start of the
+ * next frame. Note that for long frames this could be in
+ * a subsequent interrupt.
+ */
+ i = 0;
+ while ((dmabits & (DMA_OWN | RX_STP)) == 0) {
+ FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+ rxp = (rxp+1) % NUM_RX_BUFFER;
+ if (++i > NUM_RX_BUFFER) {
+ dbg(DBG_ASS, "intr_rx: Discarding more bufs"
+ " than we have\n");
+ break;
+ }
+ dmabits = FST_RDB(card, rxDescrRing[pi][rxp].bits);
+ dbg(DBG_ASS, "DMA Bits of next buffer was %x\n", dmabits);
+ }
+ dbg(DBG_ASS, "There were %d subsequent buffers in error\n", i);
+
+ /* Discard the terminal buffer */
+ if (!(dmabits & DMA_OWN)) {
+ FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+ rxp = (rxp+1) % NUM_RX_BUFFER;
+ }
+ port->rxpos = rxp;
+ return;
+
+}
+
+/* Rx complete interrupt
+ */
+static void
+fst_intr_rx(struct fst_card_info *card, struct fst_port_info *port)
+{
+ unsigned char dmabits;
+ int pi;
+ int rxp;
+ int rx_status;
+ unsigned short len;
+ struct sk_buff *skb;
+ struct net_device *dev = port_to_dev(port);
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ /* Check we have a buffer to process */
+ pi = port->index;
+ rxp = port->rxpos;
+ dmabits = FST_RDB(card, rxDescrRing[pi][rxp].bits);
+ if (dmabits & DMA_OWN) {
+ dbg(DBG_RX | DBG_INTR, "intr_rx: No buffer port %d pos %d\n",
+ pi, rxp);
+ return;
+ }
+ if (card->dmarx_in_progress) {
+ return;
+ }
+
+ /* Get buffer length */
+ len = FST_RDW(card, rxDescrRing[pi][rxp].mcnt);
+ /* Discard the CRC */
+ len -= 2;
+ if (len == 0) {
+ /*
+ * This seems to happen on the TE1 interface sometimes
+ * so throw the frame away and log the event.
+ */
+ printk_err("Frame received with 0 length. Card %d Port %d\n",
+ card->card_no, port->index);
+ /* Return descriptor to card */
+ FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+ rxp = (rxp+1) % NUM_RX_BUFFER;
+ port->rxpos = rxp;
+ return;
+ }
+
+ /* Check buffer length and for other errors. We insist on one packet
+ * in one buffer. This simplifies things greatly and since we've
+ * allocated 8K it shouldn't be a real world limitation
+ */
+ dbg(DBG_RX, "intr_rx: %d,%d: flags %x len %d\n", pi, rxp, dmabits, len);
+ if (dmabits != (RX_STP | RX_ENP) || len > LEN_RX_BUFFER - 2) {
+ fst_log_rx_error(card, port, dmabits, rxp, len);
+ fst_recover_rx_error(card, port, dmabits, rxp, len);
+ return;
+ }
+
+ /* Allocate SKB */
+ if ((skb = dev_alloc_skb(len)) == NULL) {
+ dbg(DBG_RX, "intr_rx: can't allocate buffer\n");
+
+ stats->rx_dropped++;
+
+ /* Return descriptor to card */
+ FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+ rxp = (rxp+1) % NUM_RX_BUFFER;
+ port->rxpos = rxp;
+ return;
+ }
+
+ /*
+ * We know the length we need to receive, len.
+ * It's not worth using the DMA for reads of less than
+ * FST_MIN_DMA_LEN
+ */
+
+ if ((len < FST_MIN_DMA_LEN) || (card->family == FST_FAMILY_TXP)) {
+ memcpy_fromio(skb_put(skb, len),
+ card->mem + BUF_OFFSET(rxBuffer[pi][rxp][0]),
+ len);
+
+ /* Reset buffer descriptor */
+ FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN);
+
+ /* Update stats */
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+
+ /* Push upstream */
+ dbg(DBG_RX, "Pushing frame up the stack\n");
+ if (port->mode == FST_RAW)
+ skb->protocol = farsync_type_trans(skb, dev);
+ else
+ skb->protocol = hdlc_type_trans(skb, dev);
+ rx_status = netif_rx(skb);
+ fst_process_rx_status(rx_status, port_to_dev(port)->name);
+ if (rx_status == NET_RX_DROP) {
+ stats->rx_dropped++;
+ }
+ dev->last_rx = jiffies;
+ } else {
+ card->dma_skb_rx = skb;
+ card->dma_port_rx = port;
+ card->dma_len_rx = len;
+ card->dma_rxpos = rxp;
+ fst_rx_dma(card, (char *) card->rx_dma_handle_card,
+ (char *) BUF_OFFSET(rxBuffer[pi][rxp][0]), len);
+ }
+ if (rxp != port->rxpos) {
+ dbg(DBG_ASS, "About to increment rxpos by more than 1\n");
+ dbg(DBG_ASS, "rxp = %d rxpos = %d\n", rxp, port->rxpos);
+ }
+ rxp = (rxp+1) % NUM_RX_BUFFER;
+ port->rxpos = rxp;
+}
+
+/*
+ * The bottom halfs to the ISR
+ *
+ */
+
+static void
+do_bottom_half_tx(struct fst_card_info *card)
+{
+ struct fst_port_info *port;
+ int pi;
+ int txq_length;
+ struct sk_buff *skb;
+ unsigned long flags;
+ struct net_device *dev;
+ struct net_device_stats *stats;
+
+ /*
+ * Find a free buffer for the transmit
+ * Step through each port on this card
+ */
+
+ dbg(DBG_TX, "do_bottom_half_tx\n");
+ for (pi = 0, port = card->ports; pi < card->nports; pi++, port++) {
+ if (!port->run)
+ continue;
+
+ dev = port_to_dev(port);
+ stats = hdlc_stats(dev);
+ while (!
+ (FST_RDB(card, txDescrRing[pi][port->txpos].bits) &
+ DMA_OWN)
+ && !(card->dmatx_in_progress)) {
+ /*
+ * There doesn't seem to be a txdone event per-se
+ * We seem to have to deduce it, by checking the DMA_OWN
+ * bit on the next buffer we think we can use
+ */
+ spin_lock_irqsave(&card->card_lock, flags);
+ if ((txq_length = port->txqe - port->txqs) < 0) {
+ /*
+ * This is the case where one has wrapped and the
+ * maths gives us a negative number
+ */
+ txq_length = txq_length + FST_TXQ_DEPTH;
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ if (txq_length > 0) {
+ /*
+ * There is something to send
+ */
+ spin_lock_irqsave(&card->card_lock, flags);
+ skb = port->txq[port->txqs];
+ port->txqs++;
+ if (port->txqs == FST_TXQ_DEPTH) {
+ port->txqs = 0;
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ /*
+ * copy the data and set the required indicators on the
+ * card.
+ */
+ FST_WRW(card, txDescrRing[pi][port->txpos].bcnt,
+ cnv_bcnt(skb->len));
+ if ((skb->len < FST_MIN_DMA_LEN)
+ || (card->family == FST_FAMILY_TXP)) {
+ /* Enqueue the packet with normal io */
+ memcpy_toio(card->mem +
+ BUF_OFFSET(txBuffer[pi]
+ [port->
+ txpos][0]),
+ skb->data, skb->len);
+ FST_WRB(card,
+ txDescrRing[pi][port->txpos].
+ bits,
+ DMA_OWN | TX_STP | TX_ENP);
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+ dev->trans_start = jiffies;
+ } else {
+ /* Or do it through dma */
+ memcpy(card->tx_dma_handle_host,
+ skb->data, skb->len);
+ card->dma_port_tx = port;
+ card->dma_len_tx = skb->len;
+ card->dma_txpos = port->txpos;
+ fst_tx_dma(card,
+ (char *) card->
+ tx_dma_handle_card,
+ (char *)
+ BUF_OFFSET(txBuffer[pi]
+ [port->txpos][0]),
+ skb->len);
+ }
+ if (++port->txpos >= NUM_TX_BUFFER)
+ port->txpos = 0;
+ /*
+ * If we have flow control on, can we now release it?
+ */
+ if (port->start) {
+ if (txq_length < fst_txq_low) {
+ netif_wake_queue(port_to_dev
+ (port));
+ port->start = 0;
+ }
+ }
+ dev_kfree_skb(skb);
+ } else {
+ /*
+ * Nothing to send so break out of the while loop
+ */
+ break;
+ }
+ }
+ }
+}
+
+static void
+do_bottom_half_rx(struct fst_card_info *card)
+{
+ struct fst_port_info *port;
+ int pi;
+ int rx_count = 0;
+
+ /* Check for rx completions on all ports on this card */
+ dbg(DBG_RX, "do_bottom_half_rx\n");
+ for (pi = 0, port = card->ports; pi < card->nports; pi++, port++) {
+ if (!port->run)
+ continue;
+
+ while (!(FST_RDB(card, rxDescrRing[pi][port->rxpos].bits)
+ & DMA_OWN) && !(card->dmarx_in_progress)) {
+ if (rx_count > fst_max_reads) {
+ /*
+ * Don't spend forever in receive processing
+ * Schedule another event
+ */
+ fst_q_work_item(&fst_work_intq, card->card_no);
+ tasklet_schedule(&fst_int_task);
+ break; /* Leave the loop */
+ }
+ fst_intr_rx(card, port);
+ rx_count++;
+ }
+ }
+}
+
+/*
+ * The interrupt service routine
+ * Dev_id is our fst_card_info pointer
+ */
+irqreturn_t
+fst_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct fst_card_info *card;
+ struct fst_port_info *port;
+ int rdidx; /* Event buffer indices */
+ int wridx;
+ int event; /* Actual event for processing */
+ unsigned int dma_intcsr = 0;
+ unsigned int do_card_interrupt;
+ unsigned int int_retry_count;
+
+ if ((card = dev_id) == NULL) {
+ dbg(DBG_INTR, "intr: spurious %d\n", irq);
+ return IRQ_NONE;
+ }
+
+ /*
+ * Check to see if the interrupt was for this card
+ * return if not
+ * Note that the call to clear the interrupt is important
+ */
+ dbg(DBG_INTR, "intr: %d %p\n", irq, card);
+ if (card->state != FST_RUNNING) {
+ printk_err
+ ("Interrupt received for card %d in a non running state (%d)\n",
+ card->card_no, card->state);
+
+ /*
+ * It is possible to really be running, i.e. we have re-loaded
+ * a running card
+ * Clear and reprime the interrupt source
+ */
+ fst_clear_intr(card);
+ return IRQ_HANDLED;
+ }
+
+ /* Clear and reprime the interrupt source */
+ fst_clear_intr(card);
+
+ /*
+ * Is the interrupt for this card (handshake == 1)
+ */
+ do_card_interrupt = 0;
+ if (FST_RDB(card, interruptHandshake) == 1) {
+ do_card_interrupt += FST_CARD_INT;
+ /* Set the software acknowledge */
+ FST_WRB(card, interruptHandshake, 0xEE);
+ }
+ if (card->family == FST_FAMILY_TXU) {
+ /*
+ * Is it a DMA Interrupt
+ */
+ dma_intcsr = inl(card->pci_conf + INTCSR_9054);
+ if (dma_intcsr & 0x00200000) {
+ /*
+ * DMA Channel 0 (Rx transfer complete)
+ */
+ dbg(DBG_RX, "DMA Rx xfer complete\n");
+ outb(0x8, card->pci_conf + DMACSR0);
+ fst_rx_dma_complete(card, card->dma_port_rx,
+ card->dma_len_rx, card->dma_skb_rx,
+ card->dma_rxpos);
+ card->dmarx_in_progress = 0;
+ do_card_interrupt += FST_RX_DMA_INT;
+ }
+ if (dma_intcsr & 0x00400000) {
+ /*
+ * DMA Channel 1 (Tx transfer complete)
+ */
+ dbg(DBG_TX, "DMA Tx xfer complete\n");
+ outb(0x8, card->pci_conf + DMACSR1);
+ fst_tx_dma_complete(card, card->dma_port_tx,
+ card->dma_len_tx, card->dma_txpos);
+ card->dmatx_in_progress = 0;
+ do_card_interrupt += FST_TX_DMA_INT;
+ }
+ }
+
+ /*
+ * Have we been missing Interrupts
+ */
+ int_retry_count = FST_RDL(card, interruptRetryCount);
+ if (int_retry_count) {
+ dbg(DBG_ASS, "Card %d int_retry_count is %d\n",
+ card->card_no, int_retry_count);
+ FST_WRL(card, interruptRetryCount, 0);
+ }
+
+ if (!do_card_interrupt) {
+ return IRQ_HANDLED;
+ }
+
+ /* Scehdule the bottom half of the ISR */
+ fst_q_work_item(&fst_work_intq, card->card_no);
+ tasklet_schedule(&fst_int_task);
+
+ /* Drain the event queue */
+ rdidx = FST_RDB(card, interruptEvent.rdindex) & 0x1f;
+ wridx = FST_RDB(card, interruptEvent.wrindex) & 0x1f;
+ while (rdidx != wridx) {
+ event = FST_RDB(card, interruptEvent.evntbuff[rdidx]);
+ port = &card->ports[event & 0x03];
+
+ dbg(DBG_INTR, "Processing Interrupt event: %x\n", event);
+
+ switch (event) {
+ case TE1_ALMA:
+ dbg(DBG_INTR, "TE1 Alarm intr\n");
+ if (port->run)
+ fst_intr_te1_alarm(card, port);
+ break;
+
+ case CTLA_CHG:
+ case CTLB_CHG:
+ case CTLC_CHG:
+ case CTLD_CHG:
+ if (port->run)
+ fst_intr_ctlchg(card, port);
+ break;
+
+ case ABTA_SENT:
+ case ABTB_SENT:
+ case ABTC_SENT:
+ case ABTD_SENT:
+ dbg(DBG_TX, "Abort complete port %d\n", port->index);
+ break;
+
+ case TXA_UNDF:
+ case TXB_UNDF:
+ case TXC_UNDF:
+ case TXD_UNDF:
+ /* Difficult to see how we'd get this given that we
+ * always load up the entire packet for DMA.
+ */
+ dbg(DBG_TX, "Tx underflow port %d\n", port->index);
+ hdlc_stats(port_to_dev(port))->tx_errors++;
+ hdlc_stats(port_to_dev(port))->tx_fifo_errors++;
+ dbg(DBG_ASS, "Tx underflow on card %d port %d\n",
+ card->card_no, port->index);
+ break;
+
+ case INIT_CPLT:
+ dbg(DBG_INIT, "Card init OK intr\n");
+ break;
+
+ case INIT_FAIL:
+ dbg(DBG_INIT, "Card init FAILED intr\n");
+ card->state = FST_IFAILED;
+ break;
+
+ default:
+ printk_err("intr: unknown card event %d. ignored\n",
+ event);
+ break;
+ }
+
+ /* Bump and wrap the index */
+ if (++rdidx >= MAX_CIRBUFF)
+ rdidx = 0;
+ }
+ FST_WRB(card, interruptEvent.rdindex, rdidx);
+ return IRQ_HANDLED;
+}
+
+/* Check that the shared memory configuration is one that we can handle
+ * and that some basic parameters are correct
+ */
+static void
+check_started_ok(struct fst_card_info *card)
+{
+ int i;
+
+ /* Check structure version and end marker */
+ if (FST_RDW(card, smcVersion) != SMC_VERSION) {
+ printk_err("Bad shared memory version %d expected %d\n",
+ FST_RDW(card, smcVersion), SMC_VERSION);
+ card->state = FST_BADVERSION;
+ return;
+ }
+ if (FST_RDL(card, endOfSmcSignature) != END_SIG) {
+ printk_err("Missing shared memory signature\n");
+ card->state = FST_BADVERSION;
+ return;
+ }
+ /* Firmware status flag, 0x00 = initialising, 0x01 = OK, 0xFF = fail */
+ if ((i = FST_RDB(card, taskStatus)) == 0x01) {
+ card->state = FST_RUNNING;
+ } else if (i == 0xFF) {
+ printk_err("Firmware initialisation failed. Card halted\n");
+ card->state = FST_HALTED;
+ return;
+ } else if (i != 0x00) {
+ printk_err("Unknown firmware status 0x%x\n", i);
+ card->state = FST_HALTED;
+ return;
+ }
+
+ /* Finally check the number of ports reported by firmware against the
+ * number we assumed at card detection. Should never happen with
+ * existing firmware etc so we just report it for the moment.
+ */
+ if (FST_RDL(card, numberOfPorts) != card->nports) {
+ printk_warn("Port count mismatch on card %d."
+ " Firmware thinks %d we say %d\n", card->card_no,
+ FST_RDL(card, numberOfPorts), card->nports);
+ }
+}
+
+static int
+set_conf_from_info(struct fst_card_info *card, struct fst_port_info *port,
+ struct fstioc_info *info)
+{
+ int err;
+ unsigned char my_framing;
+
+ /* Set things according to the user set valid flags
+ * Several of the old options have been invalidated/replaced by the
+ * generic hdlc package.
+ */
+ err = 0;
+ if (info->valid & FSTVAL_PROTO) {
+ if (info->proto == FST_RAW)
+ port->mode = FST_RAW;
+ else
+ port->mode = FST_GEN_HDLC;
+ }
+
+ if (info->valid & FSTVAL_CABLE)
+ err = -EINVAL;
+
+ if (info->valid & FSTVAL_SPEED)
+ err = -EINVAL;
+
+ if (info->valid & FSTVAL_PHASE)
+ FST_WRB(card, portConfig[port->index].invertClock,
+ info->invertClock);
+ if (info->valid & FSTVAL_MODE)
+ FST_WRW(card, cardMode, info->cardMode);
+ if (info->valid & FSTVAL_TE1) {
+ FST_WRL(card, suConfig.dataRate, info->lineSpeed);
+ FST_WRB(card, suConfig.clocking, info->clockSource);
+ my_framing = FRAMING_E1;
+ if (info->framing == E1)
+ my_framing = FRAMING_E1;
+ if (info->framing == T1)
+ my_framing = FRAMING_T1;
+ if (info->framing == J1)
+ my_framing = FRAMING_J1;
+ FST_WRB(card, suConfig.framing, my_framing);
+ FST_WRB(card, suConfig.structure, info->structure);
+ FST_WRB(card, suConfig.interface, info->interface);
+ FST_WRB(card, suConfig.coding, info->coding);
+ FST_WRB(card, suConfig.lineBuildOut, info->lineBuildOut);
+ FST_WRB(card, suConfig.equalizer, info->equalizer);
+ FST_WRB(card, suConfig.transparentMode, info->transparentMode);
+ FST_WRB(card, suConfig.loopMode, info->loopMode);
+ FST_WRB(card, suConfig.range, info->range);
+ FST_WRB(card, suConfig.txBufferMode, info->txBufferMode);
+ FST_WRB(card, suConfig.rxBufferMode, info->rxBufferMode);
+ FST_WRB(card, suConfig.startingSlot, info->startingSlot);
+ FST_WRB(card, suConfig.losThreshold, info->losThreshold);
+ if (info->idleCode)
+ FST_WRB(card, suConfig.enableIdleCode, 1);
+ else
+ FST_WRB(card, suConfig.enableIdleCode, 0);
+ FST_WRB(card, suConfig.idleCode, info->idleCode);
+#if FST_DEBUG
+ if (info->valid & FSTVAL_TE1) {
+ printk("Setting TE1 data\n");
+ printk("Line Speed = %d\n", info->lineSpeed);
+ printk("Start slot = %d\n", info->startingSlot);
+ printk("Clock source = %d\n", info->clockSource);
+ printk("Framing = %d\n", my_framing);
+ printk("Structure = %d\n", info->structure);
+ printk("interface = %d\n", info->interface);
+ printk("Coding = %d\n", info->coding);
+ printk("Line build out = %d\n", info->lineBuildOut);
+ printk("Equaliser = %d\n", info->equalizer);
+ printk("Transparent mode = %d\n",
+ info->transparentMode);
+ printk("Loop mode = %d\n", info->loopMode);
+ printk("Range = %d\n", info->range);
+ printk("Tx Buffer mode = %d\n", info->txBufferMode);
+ printk("Rx Buffer mode = %d\n", info->rxBufferMode);
+ printk("LOS Threshold = %d\n", info->losThreshold);
+ printk("Idle Code = %d\n", info->idleCode);
+ }
+#endif
+ }
+#if FST_DEBUG
+ if (info->valid & FSTVAL_DEBUG) {
+ fst_debug_mask = info->debug;
+ }
+#endif
+
+ return err;
+}
+
+static void
+gather_conf_info(struct fst_card_info *card, struct fst_port_info *port,
+ struct fstioc_info *info)
+{
+ int i;
+
+ memset(info, 0, sizeof (struct fstioc_info));
+
+ i = port->index;
+ info->kernelVersion = LINUX_VERSION_CODE;
+ info->nports = card->nports;
+ info->type = card->type;
+ info->state = card->state;
+ info->proto = FST_GEN_HDLC;
+ info->index = i;
+#if FST_DEBUG
+ info->debug = fst_debug_mask;
+#endif
+
+ /* Only mark information as valid if card is running.
+ * Copy the data anyway in case it is useful for diagnostics
+ */
+ info->valid = ((card->state == FST_RUNNING) ? FSTVAL_ALL : FSTVAL_CARD)
+#if FST_DEBUG
+ | FSTVAL_DEBUG
+#endif
+ ;
+
+ info->lineInterface = FST_RDW(card, portConfig[i].lineInterface);
+ info->internalClock = FST_RDB(card, portConfig[i].internalClock);
+ info->lineSpeed = FST_RDL(card, portConfig[i].lineSpeed);
+ info->invertClock = FST_RDB(card, portConfig[i].invertClock);
+ info->v24IpSts = FST_RDL(card, v24IpSts[i]);
+ info->v24OpSts = FST_RDL(card, v24OpSts[i]);
+ info->clockStatus = FST_RDW(card, clockStatus[i]);
+ info->cableStatus = FST_RDW(card, cableStatus);
+ info->cardMode = FST_RDW(card, cardMode);
+ info->smcFirmwareVersion = FST_RDL(card, smcFirmwareVersion);
+
+ /*
+ * The T2U can report cable presence for both A or B
+ * in bits 0 and 1 of cableStatus. See which port we are and
+ * do the mapping.
+ */
+ if (card->family == FST_FAMILY_TXU) {
+ if (port->index == 0) {
+ /*
+ * Port A
+ */
+ info->cableStatus = info->cableStatus & 1;
+ } else {
+ /*
+ * Port B
+ */
+ info->cableStatus = info->cableStatus >> 1;
+ info->cableStatus = info->cableStatus & 1;
+ }
+ }
+ /*
+ * Some additional bits if we are TE1
+ */
+ if (card->type == FST_TYPE_TE1) {
+ info->lineSpeed = FST_RDL(card, suConfig.dataRate);
+ info->clockSource = FST_RDB(card, suConfig.clocking);
+ info->framing = FST_RDB(card, suConfig.framing);
+ info->structure = FST_RDB(card, suConfig.structure);
+ info->interface = FST_RDB(card, suConfig.interface);
+ info->coding = FST_RDB(card, suConfig.coding);
+ info->lineBuildOut = FST_RDB(card, suConfig.lineBuildOut);
+ info->equalizer = FST_RDB(card, suConfig.equalizer);
+ info->loopMode = FST_RDB(card, suConfig.loopMode);
+ info->range = FST_RDB(card, suConfig.range);
+ info->txBufferMode = FST_RDB(card, suConfig.txBufferMode);
+ info->rxBufferMode = FST_RDB(card, suConfig.rxBufferMode);
+ info->startingSlot = FST_RDB(card, suConfig.startingSlot);
+ info->losThreshold = FST_RDB(card, suConfig.losThreshold);
+ if (FST_RDB(card, suConfig.enableIdleCode))
+ info->idleCode = FST_RDB(card, suConfig.idleCode);
+ else
+ info->idleCode = 0;
+ info->receiveBufferDelay =
+ FST_RDL(card, suStatus.receiveBufferDelay);
+ info->framingErrorCount =
+ FST_RDL(card, suStatus.framingErrorCount);
+ info->codeViolationCount =
+ FST_RDL(card, suStatus.codeViolationCount);
+ info->crcErrorCount = FST_RDL(card, suStatus.crcErrorCount);
+ info->lineAttenuation = FST_RDL(card, suStatus.lineAttenuation);
+ info->lossOfSignal = FST_RDB(card, suStatus.lossOfSignal);
+ info->receiveRemoteAlarm =
+ FST_RDB(card, suStatus.receiveRemoteAlarm);
+ info->alarmIndicationSignal =
+ FST_RDB(card, suStatus.alarmIndicationSignal);
+ }
+}
+
+static int
+fst_set_iface(struct fst_card_info *card, struct fst_port_info *port,
+ struct ifreq *ifr)
+{
+ sync_serial_settings sync;
+ int i;
+
+ if (ifr->ifr_settings.size != sizeof (sync)) {
+ return -ENOMEM;
+ }
+
+ if (copy_from_user
+ (&sync, ifr->ifr_settings.ifs_ifsu.sync, sizeof (sync))) {
+ return -EFAULT;
+ }
+
+ if (sync.loopback)
+ return -EINVAL;
+
+ i = port->index;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_IFACE_V35:
+ FST_WRW(card, portConfig[i].lineInterface, V35);
+ port->hwif = V35;
+ break;
+
+ case IF_IFACE_V24:
+ FST_WRW(card, portConfig[i].lineInterface, V24);
+ port->hwif = V24;
+ break;
+
+ case IF_IFACE_X21:
+ FST_WRW(card, portConfig[i].lineInterface, X21);
+ port->hwif = X21;
+ break;
+
+ case IF_IFACE_X21D:
+ FST_WRW(card, portConfig[i].lineInterface, X21D);
+ port->hwif = X21D;
+ break;
+
+ case IF_IFACE_T1:
+ FST_WRW(card, portConfig[i].lineInterface, T1);
+ port->hwif = T1;
+ break;
+
+ case IF_IFACE_E1:
+ FST_WRW(card, portConfig[i].lineInterface, E1);
+ port->hwif = E1;
+ break;
+
+ case IF_IFACE_SYNC_SERIAL:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (sync.clock_type) {
+ case CLOCK_EXT:
+ FST_WRB(card, portConfig[i].internalClock, EXTCLK);
+ break;
+
+ case CLOCK_INT:
+ FST_WRB(card, portConfig[i].internalClock, INTCLK);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ FST_WRL(card, portConfig[i].lineSpeed, sync.clock_rate);
+ return 0;
+}
+
+static int
+fst_get_iface(struct fst_card_info *card, struct fst_port_info *port,
+ struct ifreq *ifr)
+{
+ sync_serial_settings sync;
+ int i;
+
+ /* First check what line type is set, we'll default to reporting X.21
+ * if nothing is set as IF_IFACE_SYNC_SERIAL implies it can't be
+ * changed
+ */
+ switch (port->hwif) {
+ case E1:
+ ifr->ifr_settings.type = IF_IFACE_E1;
+ break;
+ case T1:
+ ifr->ifr_settings.type = IF_IFACE_T1;
+ break;
+ case V35:
+ ifr->ifr_settings.type = IF_IFACE_V35;
+ break;
+ case V24:
+ ifr->ifr_settings.type = IF_IFACE_V24;
+ break;
+ case X21D:
+ ifr->ifr_settings.type = IF_IFACE_X21D;
+ break;
+ case X21:
+ default:
+ ifr->ifr_settings.type = IF_IFACE_X21;
+ break;
+ }
+ if (ifr->ifr_settings.size == 0) {
+ return 0; /* only type requested */
+ }
+ if (ifr->ifr_settings.size < sizeof (sync)) {
+ return -ENOMEM;
+ }
+
+ i = port->index;
+ sync.clock_rate = FST_RDL(card, portConfig[i].lineSpeed);
+ /* Lucky card and linux use same encoding here */
+ sync.clock_type = FST_RDB(card, portConfig[i].internalClock) ==
+ INTCLK ? CLOCK_INT : CLOCK_EXT;
+ sync.loopback = 0;
+
+ if (copy_to_user(ifr->ifr_settings.ifs_ifsu.sync, &sync, sizeof (sync))) {
+ return -EFAULT;
+ }
+
+ ifr->ifr_settings.size = sizeof (sync);
+ return 0;
+}
+
+static int
+fst_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct fst_card_info *card;
+ struct fst_port_info *port;
+ struct fstioc_write wrthdr;
+ struct fstioc_info info;
+ unsigned long flags;
+
+ dbg(DBG_IOCTL, "ioctl: %x, %p\n", cmd, ifr->ifr_data);
+
+ port = dev_to_port(dev);
+ card = port->card;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case FSTCPURESET:
+ fst_cpureset(card);
+ card->state = FST_RESET;
+ return 0;
+
+ case FSTCPURELEASE:
+ fst_cpurelease(card);
+ card->state = FST_STARTING;
+ return 0;
+
+ case FSTWRITE: /* Code write (download) */
+
+ /* First copy in the header with the length and offset of data
+ * to write
+ */
+ if (ifr->ifr_data == NULL) {
+ return -EINVAL;
+ }
+ if (copy_from_user(&wrthdr, ifr->ifr_data,
+ sizeof (struct fstioc_write))) {
+ return -EFAULT;
+ }
+
+ /* Sanity check the parameters. We don't support partial writes
+ * when going over the top
+ */
+ if (wrthdr.size > FST_MEMSIZE || wrthdr.offset > FST_MEMSIZE
+ || wrthdr.size + wrthdr.offset > FST_MEMSIZE) {
+ return -ENXIO;
+ }
+
+ /* Now copy the data to the card.
+ * This will probably break on some architectures.
+ * I'll fix it when I have something to test on.
+ */
+ if (copy_from_user(card->mem + wrthdr.offset,
+ ifr->ifr_data + sizeof (struct fstioc_write),
+ wrthdr.size)) {
+ return -EFAULT;
+ }
+
+ /* Writes to the memory of a card in the reset state constitute
+ * a download
+ */
+ if (card->state == FST_RESET) {
+ card->state = FST_DOWNLOAD;
+ }
+ return 0;
+
+ case FSTGETCONF:
+
+ /* If card has just been started check the shared memory config
+ * version and marker
+ */
+ if (card->state == FST_STARTING) {
+ check_started_ok(card);
+
+ /* If everything checked out enable card interrupts */
+ if (card->state == FST_RUNNING) {
+ spin_lock_irqsave(&card->card_lock, flags);
+ fst_enable_intr(card);
+ FST_WRB(card, interruptHandshake, 0xEE);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ }
+ }
+
+ if (ifr->ifr_data == NULL) {
+ return -EINVAL;
+ }
+
+ gather_conf_info(card, port, &info);
+
+ if (copy_to_user(ifr->ifr_data, &info, sizeof (info))) {
+ return -EFAULT;
+ }
+ return 0;
+
+ case FSTSETCONF:
+
+ /*
+ * Most of the settings have been moved to the generic ioctls
+ * this just covers debug and board ident now
+ */
+
+ if (card->state != FST_RUNNING) {
+ printk_err
+ ("Attempt to configure card %d in non-running state (%d)\n",
+ card->card_no, card->state);
+ return -EIO;
+ }
+ if (copy_from_user(&info, ifr->ifr_data, sizeof (info))) {
+ return -EFAULT;
+ }
+
+ return set_conf_from_info(card, port, &info);
+
+ case SIOCWANDEV:
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ return fst_get_iface(card, port, ifr);
+
+ case IF_IFACE_SYNC_SERIAL:
+ case IF_IFACE_V35:
+ case IF_IFACE_V24:
+ case IF_IFACE_X21:
+ case IF_IFACE_X21D:
+ case IF_IFACE_T1:
+ case IF_IFACE_E1:
+ return fst_set_iface(card, port, ifr);
+
+ case IF_PROTO_RAW:
+ port->mode = FST_RAW;
+ return 0;
+
+ case IF_GET_PROTO:
+ if (port->mode == FST_RAW) {
+ ifr->ifr_settings.type = IF_PROTO_RAW;
+ return 0;
+ }
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ default:
+ port->mode = FST_GEN_HDLC;
+ dbg(DBG_IOCTL, "Passing this type to hdlc %x\n",
+ ifr->ifr_settings.type);
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+
+ default:
+ /* Not one of ours. Pass through to HDLC package */
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+static void
+fst_openport(struct fst_port_info *port)
+{
+ int signals;
+ int txq_length;
+
+ /* Only init things if card is actually running. This allows open to
+ * succeed for downloads etc.
+ */
+ if (port->card->state == FST_RUNNING) {
+ if (port->run) {
+ dbg(DBG_OPEN, "open: found port already running\n");
+
+ fst_issue_cmd(port, STOPPORT);
+ port->run = 0;
+ }
+
+ fst_rx_config(port);
+ fst_tx_config(port);
+ fst_op_raise(port, OPSTS_RTS | OPSTS_DTR);
+
+ fst_issue_cmd(port, STARTPORT);
+ port->run = 1;
+
+ signals = FST_RDL(port->card, v24DebouncedSts[port->index]);
+ if (signals & (((port->hwif == X21) || (port->hwif == X21D))
+ ? IPSTS_INDICATE : IPSTS_DCD))
+ netif_carrier_on(port_to_dev(port));
+ else
+ netif_carrier_off(port_to_dev(port));
+
+ txq_length = port->txqe - port->txqs;
+ port->txqe = 0;
+ port->txqs = 0;
+ }
+
+}
+
+static void
+fst_closeport(struct fst_port_info *port)
+{
+ if (port->card->state == FST_RUNNING) {
+ if (port->run) {
+ port->run = 0;
+ fst_op_lower(port, OPSTS_RTS | OPSTS_DTR);
+
+ fst_issue_cmd(port, STOPPORT);
+ } else {
+ dbg(DBG_OPEN, "close: port not running\n");
+ }
+ }
+}
+
+static int
+fst_open(struct net_device *dev)
+{
+ int err;
+ struct fst_port_info *port;
+
+ port = dev_to_port(dev);
+ if (!try_module_get(THIS_MODULE))
+ return -EBUSY;
+
+ if (port->mode != FST_RAW) {
+ err = hdlc_open(dev);
+ if (err)
+ return err;
+ }
+
+ fst_openport(port);
+ netif_wake_queue(dev);
+ return 0;
+}
+
+static int
+fst_close(struct net_device *dev)
+{
+ struct fst_port_info *port;
+ struct fst_card_info *card;
+ unsigned char tx_dma_done;
+ unsigned char rx_dma_done;
+
+ port = dev_to_port(dev);
+ card = port->card;
+
+ tx_dma_done = inb(card->pci_conf + DMACSR1);
+ rx_dma_done = inb(card->pci_conf + DMACSR0);
+ dbg(DBG_OPEN,
+ "Port Close: tx_dma_in_progress = %d (%x) rx_dma_in_progress = %d (%x)\n",
+ card->dmatx_in_progress, tx_dma_done, card->dmarx_in_progress,
+ rx_dma_done);
+
+ netif_stop_queue(dev);
+ fst_closeport(dev_to_port(dev));
+ if (port->mode != FST_RAW) {
+ hdlc_close(dev);
+ }
+ module_put(THIS_MODULE);
+ return 0;
+}
+
+static int
+fst_attach(struct net_device *dev, unsigned short encoding, unsigned short parity)
+{
+ /*
+ * Setting currently fixed in FarSync card so we check and forget
+ */
+ if (encoding != ENCODING_NRZ || parity != PARITY_CRC16_PR1_CCITT)
+ return -EINVAL;
+ return 0;
+}
+
+static void
+fst_tx_timeout(struct net_device *dev)
+{
+ struct fst_port_info *port;
+ struct fst_card_info *card;
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ port = dev_to_port(dev);
+ card = port->card;
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+ dbg(DBG_ASS, "Tx timeout card %d port %d\n",
+ card->card_no, port->index);
+ fst_issue_cmd(port, ABORTTX);
+
+ dev->trans_start = jiffies;
+ netif_wake_queue(dev);
+ port->start = 0;
+}
+
+static int
+fst_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct fst_card_info *card;
+ struct fst_port_info *port;
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+ int txq_length;
+
+ port = dev_to_port(dev);
+ card = port->card;
+ dbg(DBG_TX, "fst_start_xmit: length = %d\n", skb->len);
+
+ /* Drop packet with error if we don't have carrier */
+ if (!netif_carrier_ok(dev)) {
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ stats->tx_carrier_errors++;
+ dbg(DBG_ASS,
+ "Tried to transmit but no carrier on card %d port %d\n",
+ card->card_no, port->index);
+ return 0;
+ }
+
+ /* Drop it if it's too big! MTU failure ? */
+ if (skb->len > LEN_TX_BUFFER) {
+ dbg(DBG_ASS, "Packet too large %d vs %d\n", skb->len,
+ LEN_TX_BUFFER);
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ return 0;
+ }
+
+ /*
+ * We are always going to queue the packet
+ * so that the bottom half is the only place we tx from
+ * Check there is room in the port txq
+ */
+ spin_lock_irqsave(&card->card_lock, flags);
+ if ((txq_length = port->txqe - port->txqs) < 0) {
+ /*
+ * This is the case where the next free has wrapped but the
+ * last used hasn't
+ */
+ txq_length = txq_length + FST_TXQ_DEPTH;
+ }
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ if (txq_length > fst_txq_high) {
+ /*
+ * We have got enough buffers in the pipeline. Ask the network
+ * layer to stop sending frames down
+ */
+ netif_stop_queue(dev);
+ port->start = 1; /* I'm using this to signal stop sent up */
+ }
+
+ if (txq_length == FST_TXQ_DEPTH - 1) {
+ /*
+ * This shouldn't have happened but such is life
+ */
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ dbg(DBG_ASS, "Tx queue overflow card %d port %d\n",
+ card->card_no, port->index);
+ return 0;
+ }
+
+ /*
+ * queue the buffer
+ */
+ spin_lock_irqsave(&card->card_lock, flags);
+ port->txq[port->txqe] = skb;
+ port->txqe++;
+ if (port->txqe == FST_TXQ_DEPTH)
+ port->txqe = 0;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ /* Scehdule the bottom half which now does transmit processing */
+ fst_q_work_item(&fst_work_txq, card->card_no);
+ tasklet_schedule(&fst_tx_task);
+
+ return 0;
+}
+
+/*
+ * Card setup having checked hardware resources.
+ * Should be pretty bizarre if we get an error here (kernel memory
+ * exhaustion is one possibility). If we do see a problem we report it
+ * via a printk and leave the corresponding interface and all that follow
+ * disabled.
+ */
+static char *type_strings[] __devinitdata = {
+ "no hardware", /* Should never be seen */
+ "FarSync T2P",
+ "FarSync T4P",
+ "FarSync T1U",
+ "FarSync T2U",
+ "FarSync T4U",
+ "FarSync TE1"
+};
+
+static void __devinit
+fst_init_card(struct fst_card_info *card)
+{
+ int i;
+ int err;
+
+ /* We're working on a number of ports based on the card ID. If the
+ * firmware detects something different later (should never happen)
+ * we'll have to revise it in some way then.
+ */
+ for (i = 0; i < card->nports; i++) {
+ err = register_hdlc_device(card->ports[i].dev);
+ if (err < 0) {
+ int j;
+ printk_err ("Cannot register HDLC device for port %d"
+ " (errno %d)\n", i, -err );
+ for (j = i; j < card->nports; j++) {
+ free_netdev(card->ports[j].dev);
+ card->ports[j].dev = NULL;
+ }
+ card->nports = i;
+ break;
+ }
+ }
+
+ printk_info("%s-%s: %s IRQ%d, %d ports\n",
+ port_to_dev(&card->ports[0])->name,
+ port_to_dev(&card->ports[card->nports - 1])->name,
+ type_strings[card->type], card->irq, card->nports);
+}
+
+/*
+ * Initialise card when detected.
+ * Returns 0 to indicate success, or errno otherwise.
+ */
+static int __devinit
+fst_add_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ static int firsttime_done = 0;
+ static int no_of_cards_added = 0;
+ struct fst_card_info *card;
+ int err = 0;
+ int i;
+
+ if (!firsttime_done) {
+ printk_info("FarSync WAN driver " FST_USER_VERSION
+ " (c) 2001-2004 FarSite Communications Ltd.\n");
+ firsttime_done = 1;
+ dbg(DBG_ASS, "The value of debug mask is %x\n", fst_debug_mask);
+ }
+
+ /*
+ * We are going to be clever and allow certain cards not to be
+ * configured. An exclude list can be provided in /etc/modules.conf
+ */
+ if (fst_excluded_cards != 0) {
+ /*
+ * There are cards to exclude
+ *
+ */
+ for (i = 0; i < fst_excluded_cards; i++) {
+ if ((pdev->devfn) >> 3 == fst_excluded_list[i]) {
+ printk_info("FarSync PCI device %d not assigned\n",
+ (pdev->devfn) >> 3);
+ return -EBUSY;
+ }
+ }
+ }
+
+ /* Allocate driver private data */
+ card = kmalloc(sizeof (struct fst_card_info), GFP_KERNEL);
+ if (card == NULL) {
+ printk_err("FarSync card found but insufficient memory for"
+ " driver storage\n");
+ return -ENOMEM;
+ }
+ memset(card, 0, sizeof (struct fst_card_info));
+
+ /* Try to enable the device */
+ if ((err = pci_enable_device(pdev)) != 0) {
+ printk_err("Failed to enable card. Err %d\n", -err);
+ kfree(card);
+ return err;
+ }
+
+ if ((err = pci_request_regions(pdev, "FarSync")) !=0) {
+ printk_err("Failed to allocate regions. Err %d\n", -err);
+ pci_disable_device(pdev);
+ kfree(card);
+ return err;
+ }
+
+ /* Get virtual addresses of memory regions */
+ card->pci_conf = pci_resource_start(pdev, 1);
+ card->phys_mem = pci_resource_start(pdev, 2);
+ card->phys_ctlmem = pci_resource_start(pdev, 3);
+ if ((card->mem = ioremap(card->phys_mem, FST_MEMSIZE)) == NULL) {
+ printk_err("Physical memory remap failed\n");
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ kfree(card);
+ return -ENODEV;
+ }
+ if ((card->ctlmem = ioremap(card->phys_ctlmem, 0x10)) == NULL) {
+ printk_err("Control memory remap failed\n");
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ kfree(card);
+ return -ENODEV;
+ }
+ dbg(DBG_PCI, "kernel mem %p, ctlmem %p\n", card->mem, card->ctlmem);
+
+ /* Register the interrupt handler */
+ if (request_irq(pdev->irq, fst_intr, SA_SHIRQ, FST_DEV_NAME, card)) {
+ printk_err("Unable to register interrupt %d\n", card->irq);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ iounmap(card->ctlmem);
+ iounmap(card->mem);
+ kfree(card);
+ return -ENODEV;
+ }
+
+ /* Record info we need */
+ card->irq = pdev->irq;
+ card->type = ent->driver_data;
+ card->family = ((ent->driver_data == FST_TYPE_T2P) ||
+ (ent->driver_data == FST_TYPE_T4P))
+ ? FST_FAMILY_TXP : FST_FAMILY_TXU;
+ if ((ent->driver_data == FST_TYPE_T1U) ||
+ (ent->driver_data == FST_TYPE_TE1))
+ card->nports = 1;
+ else
+ card->nports = ((ent->driver_data == FST_TYPE_T2P) ||
+ (ent->driver_data == FST_TYPE_T2U)) ? 2 : 4;
+
+ card->state = FST_UNINIT;
+ spin_lock_init ( &card->card_lock );
+
+ for ( i = 0 ; i < card->nports ; i++ ) {
+ struct net_device *dev = alloc_hdlcdev(&card->ports[i]);
+ hdlc_device *hdlc;
+ if (!dev) {
+ while (i--)
+ free_netdev(card->ports[i].dev);
+ printk_err ("FarSync: out of memory\n");
+ free_irq(card->irq, card);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ iounmap(card->ctlmem);
+ iounmap(card->mem);
+ kfree(card);
+ return -ENODEV;
+ }
+ card->ports[i].dev = dev;
+ card->ports[i].card = card;
+ card->ports[i].index = i;
+ card->ports[i].run = 0;
+
+ hdlc = dev_to_hdlc(dev);
+
+ /* Fill in the net device info */
+ /* Since this is a PCI setup this is purely
+ * informational. Give them the buffer addresses
+ * and basic card I/O.
+ */
+ dev->mem_start = card->phys_mem
+ + BUF_OFFSET ( txBuffer[i][0][0]);
+ dev->mem_end = card->phys_mem
+ + BUF_OFFSET ( txBuffer[i][NUM_TX_BUFFER][0]);
+ dev->base_addr = card->pci_conf;
+ dev->irq = card->irq;
+
+ dev->tx_queue_len = FST_TX_QUEUE_LEN;
+ dev->open = fst_open;
+ dev->stop = fst_close;
+ dev->do_ioctl = fst_ioctl;
+ dev->watchdog_timeo = FST_TX_TIMEOUT;
+ dev->tx_timeout = fst_tx_timeout;
+ hdlc->attach = fst_attach;
+ hdlc->xmit = fst_start_xmit;
+ }
+
+ card->device = pdev;
+
+ dbg(DBG_PCI, "type %d nports %d irq %d\n", card->type,
+ card->nports, card->irq);
+ dbg(DBG_PCI, "conf %04x mem %08x ctlmem %08x\n",
+ card->pci_conf, card->phys_mem, card->phys_ctlmem);
+
+ /* Reset the card's processor */
+ fst_cpureset(card);
+ card->state = FST_RESET;
+
+ /* Initialise DMA (if required) */
+ fst_init_dma(card);
+
+ /* Record driver data for later use */
+ pci_set_drvdata(pdev, card);
+
+ /* Remainder of card setup */
+ fst_card_array[no_of_cards_added] = card;
+ card->card_no = no_of_cards_added++; /* Record instance and bump it */
+ fst_init_card(card);
+ if (card->family == FST_FAMILY_TXU) {
+ /*
+ * Allocate a dma buffer for transmit and receives
+ */
+ card->rx_dma_handle_host =
+ pci_alloc_consistent(card->device, FST_MAX_MTU,
+ &card->rx_dma_handle_card);
+ if (card->rx_dma_handle_host == NULL) {
+ printk_err("Could not allocate rx dma buffer\n");
+ fst_disable_intr(card);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ iounmap(card->ctlmem);
+ iounmap(card->mem);
+ kfree(card);
+ return -ENOMEM;
+ }
+ card->tx_dma_handle_host =
+ pci_alloc_consistent(card->device, FST_MAX_MTU,
+ &card->tx_dma_handle_card);
+ if (card->tx_dma_handle_host == NULL) {
+ printk_err("Could not allocate tx dma buffer\n");
+ fst_disable_intr(card);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ iounmap(card->ctlmem);
+ iounmap(card->mem);
+ kfree(card);
+ return -ENOMEM;
+ }
+ }
+ return 0; /* Success */
+}
+
+/*
+ * Cleanup and close down a card
+ */
+static void __devexit
+fst_remove_one(struct pci_dev *pdev)
+{
+ struct fst_card_info *card;
+ int i;
+
+ card = pci_get_drvdata(pdev);
+
+ for (i = 0; i < card->nports; i++) {
+ struct net_device *dev = port_to_dev(&card->ports[i]);
+ unregister_hdlc_device(dev);
+ }
+
+ fst_disable_intr(card);
+ free_irq(card->irq, card);
+
+ iounmap(card->ctlmem);
+ iounmap(card->mem);
+ pci_release_regions(pdev);
+ if (card->family == FST_FAMILY_TXU) {
+ /*
+ * Free dma buffers
+ */
+ pci_free_consistent(card->device, FST_MAX_MTU,
+ card->rx_dma_handle_host,
+ card->rx_dma_handle_card);
+ pci_free_consistent(card->device, FST_MAX_MTU,
+ card->tx_dma_handle_host,
+ card->tx_dma_handle_card);
+ }
+ fst_card_array[card->card_no] = NULL;
+}
+
+static struct pci_driver fst_driver = {
+ .name = FST_NAME,
+ .id_table = fst_pci_dev_id,
+ .probe = fst_add_one,
+ .remove = __devexit_p(fst_remove_one),
+ .suspend = NULL,
+ .resume = NULL,
+};
+
+static int __init
+fst_init(void)
+{
+ int i;
+
+ for (i = 0; i < FST_MAX_CARDS; i++)
+ fst_card_array[i] = NULL;
+ spin_lock_init(&fst_work_q_lock);
+ return pci_module_init(&fst_driver);
+}
+
+static void __exit
+fst_cleanup_module(void)
+{
+ printk_info("FarSync WAN driver unloading\n");
+ pci_unregister_driver(&fst_driver);
+}
+
+module_init(fst_init);
+module_exit(fst_cleanup_module);
diff --git a/drivers/net/wan/farsync.h b/drivers/net/wan/farsync.h
new file mode 100644
index 00000000000..d871dafa87a
--- /dev/null
+++ b/drivers/net/wan/farsync.h
@@ -0,0 +1,357 @@
+/*
+ * FarSync X21 driver for Linux
+ *
+ * Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards
+ *
+ * Copyright (C) 2001 FarSite Communications Ltd.
+ * www.farsite.co.uk
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Author: R.J.Dunlop <bob.dunlop@farsite.co.uk>
+ *
+ * For the most part this file only contains structures and information
+ * that is visible to applications outside the driver. Shared memory
+ * layout etc is internal to the driver and described within farsync.c.
+ * Overlap exists in that the values used for some fields within the
+ * ioctl interface extend into the cards firmware interface so values in
+ * this file may not be changed arbitrarily.
+ */
+
+/* What's in a name
+ *
+ * The project name for this driver is Oscar. The driver is intended to be
+ * used with the FarSite T-Series cards (T2P & T4P) running in the high
+ * speed frame shifter mode. This is sometimes referred to as X.21 mode
+ * which is a complete misnomer as the card continues to support V.24 and
+ * V.35 as well as X.21.
+ *
+ * A short common prefix is useful for routines within the driver to avoid
+ * conflict with other similar drivers and I chosen to use "fst_" for this
+ * purpose (FarSite T-series).
+ *
+ * Finally the device driver needs a short network interface name. Since
+ * "hdlc" is already in use I've chosen the even less informative "sync"
+ * for the present.
+ */
+#define FST_NAME "fst" /* In debug/info etc */
+#define FST_NDEV_NAME "sync" /* For net interface */
+#define FST_DEV_NAME "farsync" /* For misc interfaces */
+
+
+/* User version number
+ *
+ * This version number is incremented with each official release of the
+ * package and is a simplified number for normal user reference.
+ * Individual files are tracked by the version control system and may
+ * have individual versions (or IDs) that move much faster than the
+ * the release version as individual updates are tracked.
+ */
+#define FST_USER_VERSION "1.04"
+
+
+/* Ioctl call command values
+ *
+ * The first three private ioctls are used by the sync-PPP module,
+ * allowing a little room for expansion we start our numbering at 10.
+ */
+#define FSTWRITE (SIOCDEVPRIVATE+10)
+#define FSTCPURESET (SIOCDEVPRIVATE+11)
+#define FSTCPURELEASE (SIOCDEVPRIVATE+12)
+#define FSTGETCONF (SIOCDEVPRIVATE+13)
+#define FSTSETCONF (SIOCDEVPRIVATE+14)
+
+
+/* FSTWRITE
+ *
+ * Used to write a block of data (firmware etc) before the card is running
+ */
+struct fstioc_write {
+ unsigned int size;
+ unsigned int offset;
+ unsigned char data[0];
+};
+
+
+/* FSTCPURESET and FSTCPURELEASE
+ *
+ * These take no additional data.
+ * FSTCPURESET forces the cards CPU into a reset state and holds it there.
+ * FSTCPURELEASE releases the CPU from this reset state allowing it to run,
+ * the reset vector should be setup before this ioctl is run.
+ */
+
+/* FSTGETCONF and FSTSETCONF
+ *
+ * Get and set a card/ports configuration.
+ * In order to allow selective setting of items and for the kernel to
+ * indicate a partial status response the first field "valid" is a bitmask
+ * indicating which other fields in the structure are valid.
+ * Many of the field names in this structure match those used in the
+ * firmware shared memory configuration interface and come originally from
+ * the NT header file Smc.h
+ *
+ * When used with FSTGETCONF this structure should be zeroed before use.
+ * This is to allow for possible future expansion when some of the fields
+ * might be used to indicate a different (expanded) structure.
+ */
+struct fstioc_info {
+ unsigned int valid; /* Bits of structure that are valid */
+ unsigned int nports; /* Number of serial ports */
+ unsigned int type; /* Type index of card */
+ unsigned int state; /* State of card */
+ unsigned int index; /* Index of port ioctl was issued on */
+ unsigned int smcFirmwareVersion;
+ unsigned long kernelVersion; /* What Kernel version we are working with */
+ unsigned short lineInterface; /* Physical interface type */
+ unsigned char proto; /* Line protocol */
+ unsigned char internalClock; /* 1 => internal clock, 0 => external */
+ unsigned int lineSpeed; /* Speed in bps */
+ unsigned int v24IpSts; /* V.24 control input status */
+ unsigned int v24OpSts; /* V.24 control output status */
+ unsigned short clockStatus; /* lsb: 0=> present, 1=> absent */
+ unsigned short cableStatus; /* lsb: 0=> present, 1=> absent */
+ unsigned short cardMode; /* lsb: LED id mode */
+ unsigned short debug; /* Debug flags */
+ unsigned char transparentMode; /* Not used always 0 */
+ unsigned char invertClock; /* Invert clock feature for syncing */
+ unsigned char startingSlot; /* Time slot to use for start of tx */
+ unsigned char clockSource; /* External or internal */
+ unsigned char framing; /* E1, T1 or J1 */
+ unsigned char structure; /* unframed, double, crc4, f4, f12, */
+ /* f24 f72 */
+ unsigned char interface; /* rj48c or bnc */
+ unsigned char coding; /* hdb3 b8zs */
+ unsigned char lineBuildOut; /* 0, -7.5, -15, -22 */
+ unsigned char equalizer; /* short or lon haul settings */
+ unsigned char loopMode; /* various loopbacks */
+ unsigned char range; /* cable lengths */
+ unsigned char txBufferMode; /* tx elastic buffer depth */
+ unsigned char rxBufferMode; /* rx elastic buffer depth */
+ unsigned char losThreshold; /* Attenuation on LOS signal */
+ unsigned char idleCode; /* Value to send as idle timeslot */
+ unsigned int receiveBufferDelay; /* delay thro rx buffer timeslots */
+ unsigned int framingErrorCount; /* framing errors */
+ unsigned int codeViolationCount; /* code violations */
+ unsigned int crcErrorCount; /* CRC errors */
+ int lineAttenuation; /* in dB*/
+ unsigned short lossOfSignal;
+ unsigned short receiveRemoteAlarm;
+ unsigned short alarmIndicationSignal;
+};
+
+/* "valid" bitmask */
+#define FSTVAL_NONE 0x00000000 /* Nothing valid (firmware not running).
+ * Slight misnomer. In fact nports,
+ * type, state and index will be set
+ * based on hardware detected.
+ */
+#define FSTVAL_OMODEM 0x0000001F /* First 5 bits correspond to the
+ * output status bits defined for
+ * v24OpSts
+ */
+#define FSTVAL_SPEED 0x00000020 /* internalClock, lineSpeed, clockStatus
+ */
+#define FSTVAL_CABLE 0x00000040 /* lineInterface, cableStatus */
+#define FSTVAL_IMODEM 0x00000080 /* v24IpSts */
+#define FSTVAL_CARD 0x00000100 /* nports, type, state, index,
+ * smcFirmwareVersion
+ */
+#define FSTVAL_PROTO 0x00000200 /* proto */
+#define FSTVAL_MODE 0x00000400 /* cardMode */
+#define FSTVAL_PHASE 0x00000800 /* Clock phase */
+#define FSTVAL_TE1 0x00001000 /* T1E1 Configuration */
+#define FSTVAL_DEBUG 0x80000000 /* debug */
+#define FSTVAL_ALL 0x00001FFF /* Note: does not include DEBUG flag */
+
+/* "type" */
+#define FST_TYPE_NONE 0 /* Probably should never happen */
+#define FST_TYPE_T2P 1 /* T2P X21 2 port card */
+#define FST_TYPE_T4P 2 /* T4P X21 4 port card */
+#define FST_TYPE_T1U 3 /* T1U X21 1 port card */
+#define FST_TYPE_T2U 4 /* T2U X21 2 port card */
+#define FST_TYPE_T4U 5 /* T4U X21 4 port card */
+#define FST_TYPE_TE1 6 /* T1E1 X21 1 port card */
+
+/* "family" */
+#define FST_FAMILY_TXP 0 /* T2P or T4P */
+#define FST_FAMILY_TXU 1 /* T1U or T2U or T4U */
+
+/* "state" */
+#define FST_UNINIT 0 /* Raw uninitialised state following
+ * system startup */
+#define FST_RESET 1 /* Processor held in reset state */
+#define FST_DOWNLOAD 2 /* Card being downloaded */
+#define FST_STARTING 3 /* Released following download */
+#define FST_RUNNING 4 /* Processor running */
+#define FST_BADVERSION 5 /* Bad shared memory version detected */
+#define FST_HALTED 6 /* Processor flagged a halt */
+#define FST_IFAILED 7 /* Firmware issued initialisation failed
+ * interrupt
+ */
+/* "lineInterface" */
+#define V24 1
+#define X21 2
+#define V35 3
+#define X21D 4
+#define T1 5
+#define E1 6
+#define J1 7
+
+/* "proto" */
+#define FST_HDLC 1 /* Cisco compatible HDLC */
+#define FST_PPP 2 /* Sync PPP */
+#define FST_MONITOR 3 /* Monitor only (raw packet reception) */
+#define FST_RAW 4 /* Two way raw packets */
+#define FST_GEN_HDLC 5 /* Using "Generic HDLC" module */
+
+/* "internalClock" */
+#define INTCLK 1
+#define EXTCLK 0
+
+/* "v24IpSts" bitmask */
+#define IPSTS_CTS 0x00000001 /* Clear To Send (Indicate for X.21) */
+#define IPSTS_INDICATE IPSTS_CTS
+#define IPSTS_DSR 0x00000002 /* Data Set Ready (T2P Port A) */
+#define IPSTS_DCD 0x00000004 /* Data Carrier Detect */
+#define IPSTS_RI 0x00000008 /* Ring Indicator (T2P Port A) */
+#define IPSTS_TMI 0x00000010 /* Test Mode Indicator (Not Supported)*/
+
+/* "v24OpSts" bitmask */
+#define OPSTS_RTS 0x00000001 /* Request To Send (Control for X.21) */
+#define OPSTS_CONTROL OPSTS_RTS
+#define OPSTS_DTR 0x00000002 /* Data Terminal Ready */
+#define OPSTS_DSRS 0x00000004 /* Data Signalling Rate Select (Not
+ * Supported) */
+#define OPSTS_SS 0x00000008 /* Select Standby (Not Supported) */
+#define OPSTS_LL 0x00000010 /* Maintenance Test (Not Supported) */
+
+/* "cardMode" bitmask */
+#define CARD_MODE_IDENTIFY 0x0001
+
+/*
+ * Constants for T1/E1 configuration
+ */
+
+/*
+ * Clock source
+ */
+#define CLOCKING_SLAVE 0
+#define CLOCKING_MASTER 1
+
+/*
+ * Framing
+ */
+#define FRAMING_E1 0
+#define FRAMING_J1 1
+#define FRAMING_T1 2
+
+/*
+ * Structure
+ */
+#define STRUCTURE_UNFRAMED 0
+#define STRUCTURE_E1_DOUBLE 1
+#define STRUCTURE_E1_CRC4 2
+#define STRUCTURE_E1_CRC4M 3
+#define STRUCTURE_T1_4 4
+#define STRUCTURE_T1_12 5
+#define STRUCTURE_T1_24 6
+#define STRUCTURE_T1_72 7
+
+/*
+ * Interface
+ */
+#define INTERFACE_RJ48C 0
+#define INTERFACE_BNC 1
+
+/*
+ * Coding
+ */
+
+#define CODING_HDB3 0
+#define CODING_NRZ 1
+#define CODING_CMI 2
+#define CODING_CMI_HDB3 3
+#define CODING_CMI_B8ZS 4
+#define CODING_AMI 5
+#define CODING_AMI_ZCS 6
+#define CODING_B8ZS 7
+
+/*
+ * Line Build Out
+ */
+#define LBO_0dB 0
+#define LBO_7dB5 1
+#define LBO_15dB 2
+#define LBO_22dB5 3
+
+/*
+ * Range for long haul t1 > 655ft
+ */
+#define RANGE_0_133_FT 0
+#define RANGE_0_40_M RANGE_0_133_FT
+#define RANGE_133_266_FT 1
+#define RANGE_40_81_M RANGE_133_266_FT
+#define RANGE_266_399_FT 2
+#define RANGE_81_122_M RANGE_266_399_FT
+#define RANGE_399_533_FT 3
+#define RANGE_122_162_M RANGE_399_533_FT
+#define RANGE_533_655_FT 4
+#define RANGE_162_200_M RANGE_533_655_FT
+/*
+ * Receive Equaliser
+ */
+#define EQUALIZER_SHORT 0
+#define EQUALIZER_LONG 1
+
+/*
+ * Loop modes
+ */
+#define LOOP_NONE 0
+#define LOOP_LOCAL 1
+#define LOOP_PAYLOAD_EXC_TS0 2
+#define LOOP_PAYLOAD_INC_TS0 3
+#define LOOP_REMOTE 4
+
+/*
+ * Buffer modes
+ */
+#define BUFFER_2_FRAME 0
+#define BUFFER_1_FRAME 1
+#define BUFFER_96_BIT 2
+#define BUFFER_NONE 3
+
+/* Debug support
+ *
+ * These should only be enabled for development kernels, production code
+ * should define FST_DEBUG=0 in order to exclude the code.
+ * Setting FST_DEBUG=1 will include all the debug code but in a disabled
+ * state, use the FSTSETCONF ioctl to enable specific debug actions, or
+ * FST_DEBUG can be set to prime the debug selection.
+ */
+#define FST_DEBUG 0x0000
+#if FST_DEBUG
+
+extern int fst_debug_mask; /* Bit mask of actions to debug, bits
+ * listed below. Note: Bit 0 is used
+ * to trigger the inclusion of this
+ * code, without enabling any actions.
+ */
+#define DBG_INIT 0x0002 /* Card detection and initialisation */
+#define DBG_OPEN 0x0004 /* Open and close sequences */
+#define DBG_PCI 0x0008 /* PCI config operations */
+#define DBG_IOCTL 0x0010 /* Ioctls and other config */
+#define DBG_INTR 0x0020 /* Interrupt routines (be careful) */
+#define DBG_TX 0x0040 /* Packet transmission */
+#define DBG_RX 0x0080 /* Packet reception */
+#define DBG_CMD 0x0100 /* Port command issuing */
+
+#define DBG_ASS 0xFFFF /* Assert like statements. Code that
+ * should never be reached, if you see
+ * one of these then I've been an ass
+ */
+#endif /* FST_DEBUG */
+
diff --git a/drivers/net/wan/hd64570.h b/drivers/net/wan/hd64570.h
new file mode 100644
index 00000000000..3839662ff20
--- /dev/null
+++ b/drivers/net/wan/hd64570.h
@@ -0,0 +1,241 @@
+#ifndef __HD64570_H
+#define __HD64570_H
+
+/* SCA HD64570 register definitions - all addresses for mode 0 (8086 MPU)
+ and 1 (64180 MPU). For modes 2 and 3, XOR the address with 0x01.
+
+ Source: HD64570 SCA User's Manual
+*/
+
+
+
+/* SCA Control Registers */
+#define LPR 0x00 /* Low Power */
+
+/* Wait controller registers */
+#define PABR0 0x02 /* Physical Address Boundary 0 */
+#define PABR1 0x03 /* Physical Address Boundary 1 */
+#define WCRL 0x04 /* Wait Control L */
+#define WCRM 0x05 /* Wait Control M */
+#define WCRH 0x06 /* Wait Control H */
+
+#define PCR 0x08 /* DMA Priority Control */
+#define DMER 0x09 /* DMA Master Enable */
+
+
+/* Interrupt registers */
+#define ISR0 0x10 /* Interrupt Status 0 */
+#define ISR1 0x11 /* Interrupt Status 1 */
+#define ISR2 0x12 /* Interrupt Status 2 */
+
+#define IER0 0x14 /* Interrupt Enable 0 */
+#define IER1 0x15 /* Interrupt Enable 1 */
+#define IER2 0x16 /* Interrupt Enable 2 */
+
+#define ITCR 0x18 /* Interrupt Control */
+#define IVR 0x1A /* Interrupt Vector */
+#define IMVR 0x1C /* Interrupt Modified Vector */
+
+
+
+/* MSCI channel (port) 0 registers - offset 0x20
+ MSCI channel (port) 1 registers - offset 0x40 */
+
+#define MSCI0_OFFSET 0x20
+#define MSCI1_OFFSET 0x40
+
+#define TRBL 0x00 /* TX/RX buffer L */
+#define TRBH 0x01 /* TX/RX buffer H */
+#define ST0 0x02 /* Status 0 */
+#define ST1 0x03 /* Status 1 */
+#define ST2 0x04 /* Status 2 */
+#define ST3 0x05 /* Status 3 */
+#define FST 0x06 /* Frame Status */
+#define IE0 0x08 /* Interrupt Enable 0 */
+#define IE1 0x09 /* Interrupt Enable 1 */
+#define IE2 0x0A /* Interrupt Enable 2 */
+#define FIE 0x0B /* Frame Interrupt Enable */
+#define CMD 0x0C /* Command */
+#define MD0 0x0E /* Mode 0 */
+#define MD1 0x0F /* Mode 1 */
+#define MD2 0x10 /* Mode 2 */
+#define CTL 0x11 /* Control */
+#define SA0 0x12 /* Sync/Address 0 */
+#define SA1 0x13 /* Sync/Address 1 */
+#define IDL 0x14 /* Idle Pattern */
+#define TMC 0x15 /* Time Constant */
+#define RXS 0x16 /* RX Clock Source */
+#define TXS 0x17 /* TX Clock Source */
+#define TRC0 0x18 /* TX Ready Control 0 */
+#define TRC1 0x19 /* TX Ready Control 1 */
+#define RRC 0x1A /* RX Ready Control */
+#define CST0 0x1C /* Current Status 0 */
+#define CST1 0x1D /* Current Status 1 */
+
+
+/* Timer channel 0 (port 0 RX) registers - offset 0x60
+ Timer channel 1 (port 0 TX) registers - offset 0x68
+ Timer channel 2 (port 1 RX) registers - offset 0x70
+ Timer channel 3 (port 1 TX) registers - offset 0x78
+*/
+
+#define TIMER0RX_OFFSET 0x60
+#define TIMER0TX_OFFSET 0x68
+#define TIMER1RX_OFFSET 0x70
+#define TIMER1TX_OFFSET 0x78
+
+#define TCNTL 0x00 /* Up-counter L */
+#define TCNTH 0x01 /* Up-counter H */
+#define TCONRL 0x02 /* Constant L */
+#define TCONRH 0x03 /* Constant H */
+#define TCSR 0x04 /* Control/Status */
+#define TEPR 0x05 /* Expand Prescale */
+
+
+
+/* DMA channel 0 (port 0 RX) registers - offset 0x80
+ DMA channel 1 (port 0 TX) registers - offset 0xA0
+ DMA channel 2 (port 1 RX) registers - offset 0xC0
+ DMA channel 3 (port 1 TX) registers - offset 0xE0
+*/
+
+#define DMAC0RX_OFFSET 0x80
+#define DMAC0TX_OFFSET 0xA0
+#define DMAC1RX_OFFSET 0xC0
+#define DMAC1TX_OFFSET 0xE0
+
+#define BARL 0x00 /* Buffer Address L (chained block) */
+#define BARH 0x01 /* Buffer Address H (chained block) */
+#define BARB 0x02 /* Buffer Address B (chained block) */
+
+#define DARL 0x00 /* RX Destination Addr L (single block) */
+#define DARH 0x01 /* RX Destination Addr H (single block) */
+#define DARB 0x02 /* RX Destination Addr B (single block) */
+
+#define SARL 0x04 /* TX Source Address L (single block) */
+#define SARH 0x05 /* TX Source Address H (single block) */
+#define SARB 0x06 /* TX Source Address B (single block) */
+
+#define CPB 0x06 /* Chain Pointer Base (chained block) */
+
+#define CDAL 0x08 /* Current Descriptor Addr L (chained block) */
+#define CDAH 0x09 /* Current Descriptor Addr H (chained block) */
+#define EDAL 0x0A /* Error Descriptor Addr L (chained block) */
+#define EDAH 0x0B /* Error Descriptor Addr H (chained block) */
+#define BFLL 0x0C /* RX Receive Buffer Length L (chained block)*/
+#define BFLH 0x0D /* RX Receive Buffer Length H (chained block)*/
+#define BCRL 0x0E /* Byte Count L */
+#define BCRH 0x0F /* Byte Count H */
+#define DSR 0x10 /* DMA Status */
+#define DSR_RX(node) (DSR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DSR_TX(node) (DSR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define DMR 0x11 /* DMA Mode */
+#define DMR_RX(node) (DMR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DMR_TX(node) (DMR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define FCT 0x13 /* Frame End Interrupt Counter */
+#define FCT_RX(node) (FCT + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define FCT_TX(node) (FCT + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define DIR 0x14 /* DMA Interrupt Enable */
+#define DIR_RX(node) (DIR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DIR_TX(node) (DIR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+#define DCR 0x15 /* DMA Command */
+#define DCR_RX(node) (DCR + (node ? DMAC1RX_OFFSET : DMAC0RX_OFFSET))
+#define DCR_TX(node) (DCR + (node ? DMAC1TX_OFFSET : DMAC0TX_OFFSET))
+
+
+
+
+/* Descriptor Structure */
+
+typedef struct {
+ u16 cp; /* Chain Pointer */
+ u32 bp; /* Buffer Pointer (24 bits) */
+ u16 len; /* Data Length */
+ u8 stat; /* Status */
+ u8 unused; /* pads to 2-byte boundary */
+}__attribute__ ((packed)) pkt_desc;
+
+
+/* Packet Descriptor Status bits */
+
+#define ST_TX_EOM 0x80 /* End of frame */
+#define ST_TX_EOT 0x01 /* End of transmition */
+
+#define ST_RX_EOM 0x80 /* End of frame */
+#define ST_RX_SHORT 0x40 /* Short frame */
+#define ST_RX_ABORT 0x20 /* Abort */
+#define ST_RX_RESBIT 0x10 /* Residual bit */
+#define ST_RX_OVERRUN 0x08 /* Overrun */
+#define ST_RX_CRC 0x04 /* CRC */
+
+#define ST_ERROR_MASK 0x7C
+
+#define DIR_EOTE 0x80 /* Transfer completed */
+#define DIR_EOME 0x40 /* Frame Transfer Completed (chained-block) */
+#define DIR_BOFE 0x20 /* Buffer Overflow/Underflow (chained-block)*/
+#define DIR_COFE 0x10 /* Counter Overflow (chained-block) */
+
+
+#define DSR_EOT 0x80 /* Transfer completed */
+#define DSR_EOM 0x40 /* Frame Transfer Completed (chained-block) */
+#define DSR_BOF 0x20 /* Buffer Overflow/Underflow (chained-block)*/
+#define DSR_COF 0x10 /* Counter Overflow (chained-block) */
+#define DSR_DE 0x02 /* DMA Enable */
+#define DSR_DWE 0x01 /* DMA Write Disable */
+
+/* DMA Master Enable Register (DMER) bits */
+#define DMER_DME 0x80 /* DMA Master Enable */
+
+
+#define CMD_RESET 0x21 /* Reset Channel */
+#define CMD_TX_ENABLE 0x02 /* Start transmitter */
+#define CMD_RX_ENABLE 0x12 /* Start receiver */
+
+#define MD0_HDLC 0x80 /* Bit-sync HDLC mode */
+#define MD0_CRC_ENA 0x04 /* Enable CRC code calculation */
+#define MD0_CRC_CCITT 0x02 /* CCITT CRC instead of CRC-16 */
+#define MD0_CRC_PR1 0x01 /* Initial all-ones instead of all-zeros */
+
+#define MD0_CRC_NONE 0x00
+#define MD0_CRC_16_0 0x04
+#define MD0_CRC_16 0x05
+#define MD0_CRC_ITU_0 0x06
+#define MD0_CRC_ITU 0x07
+
+#define MD2_NRZ 0x00
+#define MD2_NRZI 0x20
+#define MD2_MANCHESTER 0x80
+#define MD2_FM_MARK 0xA0
+#define MD2_FM_SPACE 0xC0
+#define MD2_LOOPBACK 0x03 /* Local data Loopback */
+
+#define CTL_NORTS 0x01
+#define CTL_IDLE 0x10 /* Transmit an idle pattern */
+#define CTL_UDRNC 0x20 /* Idle after CRC or FCS+flag transmition */
+
+#define ST0_TXRDY 0x02 /* TX ready */
+#define ST0_RXRDY 0x01 /* RX ready */
+
+#define ST1_UDRN 0x80 /* MSCI TX underrun */
+#define ST1_CDCD 0x04 /* DCD level changed */
+
+#define ST3_CTS 0x08 /* modem input - /CTS */
+#define ST3_DCD 0x04 /* modem input - /DCD */
+
+#define IE0_TXINT 0x80 /* TX INT MSCI interrupt enable */
+#define IE0_RXINTA 0x40 /* RX INT A MSCI interrupt enable */
+#define IE1_UDRN 0x80 /* TX underrun MSCI interrupt enable */
+#define IE1_CDCD 0x04 /* DCD level changed */
+
+#define DCR_ABORT 0x01 /* Software abort command */
+#define DCR_CLEAR_EOF 0x02 /* Clear EOF interrupt */
+
+/* TX and RX Clock Source - RXS and TXS */
+#define CLK_BRG_MASK 0x0F
+#define CLK_LINE_RX 0x00 /* TX/RX clock line input */
+#define CLK_LINE_TX 0x00 /* TX/RX line input */
+#define CLK_BRG_RX 0x40 /* internal baud rate generator */
+#define CLK_BRG_TX 0x40 /* internal baud rate generator */
+#define CLK_RXCLK_TX 0x60 /* TX clock from RX clock */
+
+#endif
diff --git a/drivers/net/wan/hd64572.h b/drivers/net/wan/hd64572.h
new file mode 100644
index 00000000000..96567c2dc4d
--- /dev/null
+++ b/drivers/net/wan/hd64572.h
@@ -0,0 +1,527 @@
+/*
+ * hd64572.h Description of the Hitachi HD64572 (SCA-II), valid for
+ * CPU modes 0 & 2.
+ *
+ * Author: Ivan Passos <ivan@cyclades.com>
+ *
+ * Copyright: (c) 2000-2001 Cyclades Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Log: hd64572.h,v $
+ * Revision 3.1 2001/06/15 12:41:10 regina
+ * upping major version number
+ *
+ * Revision 1.1.1.1 2001/06/13 20:24:49 daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 1.0 2000/01/25 ivan
+ * Initial version.
+ *
+ */
+
+#ifndef __HD64572_H
+#define __HD64572_H
+
+/* Illegal Access Register */
+#define ILAR 0x00
+
+/* Wait Controller Registers */
+#define PABR0L 0x20 /* Physical Addr Boundary Register 0 L */
+#define PABR0H 0x21 /* Physical Addr Boundary Register 0 H */
+#define PABR1L 0x22 /* Physical Addr Boundary Register 1 L */
+#define PABR1H 0x23 /* Physical Addr Boundary Register 1 H */
+#define WCRL 0x24 /* Wait Control Register L */
+#define WCRM 0x25 /* Wait Control Register M */
+#define WCRH 0x26 /* Wait Control Register H */
+
+/* Interrupt Registers */
+#define IVR 0x60 /* Interrupt Vector Register */
+#define IMVR 0x64 /* Interrupt Modified Vector Register */
+#define ITCR 0x68 /* Interrupt Control Register */
+#define ISR0 0x6c /* Interrupt Status Register 0 */
+#define ISR1 0x70 /* Interrupt Status Register 1 */
+#define IER0 0x74 /* Interrupt Enable Register 0 */
+#define IER1 0x78 /* Interrupt Enable Register 1 */
+
+/* Register Access Macros (chan is 0 or 1 in _any_ case) */
+#define M_REG(reg, chan) (reg + 0x80*chan) /* MSCI */
+#define DRX_REG(reg, chan) (reg + 0x40*chan) /* DMA Rx */
+#define DTX_REG(reg, chan) (reg + 0x20*(2*chan + 1)) /* DMA Tx */
+#define TRX_REG(reg, chan) (reg + 0x20*chan) /* Timer Rx */
+#define TTX_REG(reg, chan) (reg + 0x10*(2*chan + 1)) /* Timer Tx */
+#define ST_REG(reg, chan) (reg + 0x80*chan) /* Status Cnt */
+#define IR0_DRX(val, chan) ((val)<<(8*(chan))) /* Int DMA Rx */
+#define IR0_DTX(val, chan) ((val)<<(4*(2*chan + 1))) /* Int DMA Tx */
+#define IR0_M(val, chan) ((val)<<(8*(chan))) /* Int MSCI */
+
+/* MSCI Channel Registers */
+#define MSCI0_OFFSET 0x00
+#define MSCI1_OFFSET 0x80
+
+#define MD0 0x138 /* Mode reg 0 */
+#define MD1 0x139 /* Mode reg 1 */
+#define MD2 0x13a /* Mode reg 2 */
+#define MD3 0x13b /* Mode reg 3 */
+#define CTL 0x130 /* Control reg */
+#define RXS 0x13c /* RX clock source */
+#define TXS 0x13d /* TX clock source */
+#define EXS 0x13e /* External clock input selection */
+#define TMCT 0x144 /* Time constant (Tx) */
+#define TMCR 0x145 /* Time constant (Rx) */
+#define CMD 0x128 /* Command reg */
+#define ST0 0x118 /* Status reg 0 */
+#define ST1 0x119 /* Status reg 1 */
+#define ST2 0x11a /* Status reg 2 */
+#define ST3 0x11b /* Status reg 3 */
+#define ST4 0x11c /* Status reg 4 */
+#define FST 0x11d /* frame Status reg */
+#define IE0 0x120 /* Interrupt enable reg 0 */
+#define IE1 0x121 /* Interrupt enable reg 1 */
+#define IE2 0x122 /* Interrupt enable reg 2 */
+#define IE4 0x124 /* Interrupt enable reg 4 */
+#define FIE 0x125 /* Frame Interrupt enable reg */
+#define SA0 0x140 /* Syn Address reg 0 */
+#define SA1 0x141 /* Syn Address reg 1 */
+#define IDL 0x142 /* Idle register */
+#define TRBL 0x100 /* TX/RX buffer reg L */
+#define TRBK 0x101 /* TX/RX buffer reg K */
+#define TRBJ 0x102 /* TX/RX buffer reg J */
+#define TRBH 0x103 /* TX/RX buffer reg H */
+#define TRC0 0x148 /* TX Ready control reg 0 */
+#define TRC1 0x149 /* TX Ready control reg 1 */
+#define RRC 0x14a /* RX Ready control reg */
+#define CST0 0x108 /* Current Status Register 0 */
+#define CST1 0x109 /* Current Status Register 1 */
+#define CST2 0x10a /* Current Status Register 2 */
+#define CST3 0x10b /* Current Status Register 3 */
+#define GPO 0x131 /* General Purpose Output Pin Ctl Reg */
+#define TFS 0x14b /* Tx Start Threshold Ctl Reg */
+#define TFN 0x143 /* Inter-transmit-frame Time Fill Ctl Reg */
+#define TBN 0x110 /* Tx Buffer Number Reg */
+#define RBN 0x111 /* Rx Buffer Number Reg */
+#define TNR0 0x150 /* Tx DMA Request Ctl Reg 0 */
+#define TNR1 0x151 /* Tx DMA Request Ctl Reg 1 */
+#define TCR 0x152 /* Tx DMA Critical Request Reg */
+#define RNR 0x154 /* Rx DMA Request Ctl Reg */
+#define RCR 0x156 /* Rx DMA Critical Request Reg */
+
+/* Timer Registers */
+#define TIMER0RX_OFFSET 0x00
+#define TIMER0TX_OFFSET 0x10
+#define TIMER1RX_OFFSET 0x20
+#define TIMER1TX_OFFSET 0x30
+
+#define TCNTL 0x200 /* Timer Upcounter L */
+#define TCNTH 0x201 /* Timer Upcounter H */
+#define TCONRL 0x204 /* Timer Constant Register L */
+#define TCONRH 0x205 /* Timer Constant Register H */
+#define TCSR 0x206 /* Timer Control/Status Register */
+#define TEPR 0x207 /* Timer Expand Prescale Register */
+
+/* DMA registers */
+#define PCR 0x40 /* DMA priority control reg */
+#define DRR 0x44 /* DMA reset reg */
+#define DMER 0x07 /* DMA Master Enable reg */
+#define BTCR 0x08 /* Burst Tx Ctl Reg */
+#define BOLR 0x0c /* Back-off Length Reg */
+#define DSR_RX(chan) (0x48 + 2*chan) /* DMA Status Reg (Rx) */
+#define DSR_TX(chan) (0x49 + 2*chan) /* DMA Status Reg (Tx) */
+#define DIR_RX(chan) (0x4c + 2*chan) /* DMA Interrupt Enable Reg (Rx) */
+#define DIR_TX(chan) (0x4d + 2*chan) /* DMA Interrupt Enable Reg (Tx) */
+#define FCT_RX(chan) (0x50 + 2*chan) /* Frame End Interrupt Counter (Rx) */
+#define FCT_TX(chan) (0x51 + 2*chan) /* Frame End Interrupt Counter (Tx) */
+#define DMR_RX(chan) (0x54 + 2*chan) /* DMA Mode Reg (Rx) */
+#define DMR_TX(chan) (0x55 + 2*chan) /* DMA Mode Reg (Tx) */
+#define DCR_RX(chan) (0x58 + 2*chan) /* DMA Command Reg (Rx) */
+#define DCR_TX(chan) (0x59 + 2*chan) /* DMA Command Reg (Tx) */
+
+/* DMA Channel Registers */
+#define DMAC0RX_OFFSET 0x00
+#define DMAC0TX_OFFSET 0x20
+#define DMAC1RX_OFFSET 0x40
+#define DMAC1TX_OFFSET 0x60
+
+#define DARL 0x80 /* Dest Addr Register L (single-block, RX only) */
+#define DARH 0x81 /* Dest Addr Register H (single-block, RX only) */
+#define DARB 0x82 /* Dest Addr Register B (single-block, RX only) */
+#define DARBH 0x83 /* Dest Addr Register BH (single-block, RX only) */
+#define SARL 0x80 /* Source Addr Register L (single-block, TX only) */
+#define SARH 0x81 /* Source Addr Register H (single-block, TX only) */
+#define SARB 0x82 /* Source Addr Register B (single-block, TX only) */
+#define DARBH 0x83 /* Source Addr Register BH (single-block, TX only) */
+#define BARL 0x80 /* Buffer Addr Register L (chained-block) */
+#define BARH 0x81 /* Buffer Addr Register H (chained-block) */
+#define BARB 0x82 /* Buffer Addr Register B (chained-block) */
+#define BARBH 0x83 /* Buffer Addr Register BH (chained-block) */
+#define CDAL 0x84 /* Current Descriptor Addr Register L */
+#define CDAH 0x85 /* Current Descriptor Addr Register H */
+#define CDAB 0x86 /* Current Descriptor Addr Register B */
+#define CDABH 0x87 /* Current Descriptor Addr Register BH */
+#define EDAL 0x88 /* Error Descriptor Addr Register L */
+#define EDAH 0x89 /* Error Descriptor Addr Register H */
+#define EDAB 0x8a /* Error Descriptor Addr Register B */
+#define EDABH 0x8b /* Error Descriptor Addr Register BH */
+#define BFLL 0x90 /* RX Buffer Length L (only RX) */
+#define BFLH 0x91 /* RX Buffer Length H (only RX) */
+#define BCRL 0x8c /* Byte Count Register L */
+#define BCRH 0x8d /* Byte Count Register H */
+
+/* Block Descriptor Structure */
+typedef struct {
+ unsigned long next; /* pointer to next block descriptor */
+ unsigned long ptbuf; /* buffer pointer */
+ unsigned short len; /* data length */
+ unsigned char status; /* status */
+ unsigned char filler[5]; /* alignment filler (16 bytes) */
+} pcsca_bd_t;
+
+/* Block Descriptor Structure */
+typedef struct {
+ u32 cp; /* pointer to next block descriptor */
+ u32 bp; /* buffer pointer */
+ u16 len; /* data length */
+ u8 stat; /* status */
+ u8 unused; /* pads to 4-byte boundary */
+}pkt_desc;
+
+
+/*
+ Descriptor Status definitions:
+
+ Bit Transmission Reception
+
+ 7 EOM EOM
+ 6 - Short Frame
+ 5 - Abort
+ 4 - Residual bit
+ 3 Underrun Overrun
+ 2 - CRC
+ 1 Ownership Ownership
+ 0 EOT -
+*/
+#define DST_EOT 0x01 /* End of transmit command */
+#define DST_OSB 0x02 /* Ownership bit */
+#define DST_CRC 0x04 /* CRC Error */
+#define DST_OVR 0x08 /* Overrun */
+#define DST_UDR 0x08 /* Underrun */
+#define DST_RBIT 0x10 /* Residual bit */
+#define DST_ABT 0x20 /* Abort */
+#define DST_SHRT 0x40 /* Short Frame */
+#define DST_EOM 0x80 /* End of Message */
+
+/* Packet Descriptor Status bits */
+
+#define ST_TX_EOM 0x80 /* End of frame */
+#define ST_TX_UNDRRUN 0x08
+#define ST_TX_OWNRSHP 0x02
+#define ST_TX_EOT 0x01 /* End of transmition */
+
+#define ST_RX_EOM 0x80 /* End of frame */
+#define ST_RX_SHORT 0x40 /* Short frame */
+#define ST_RX_ABORT 0x20 /* Abort */
+#define ST_RX_RESBIT 0x10 /* Residual bit */
+#define ST_RX_OVERRUN 0x08 /* Overrun */
+#define ST_RX_CRC 0x04 /* CRC */
+#define ST_RX_OWNRSHP 0x02
+
+#define ST_ERROR_MASK 0x7C
+
+/* Status Counter Registers */
+#define CMCR 0x158 /* Counter Master Ctl Reg */
+#define TECNTL 0x160 /* Tx EOM Counter L */
+#define TECNTM 0x161 /* Tx EOM Counter M */
+#define TECNTH 0x162 /* Tx EOM Counter H */
+#define TECCR 0x163 /* Tx EOM Counter Ctl Reg */
+#define URCNTL 0x164 /* Underrun Counter L */
+#define URCNTH 0x165 /* Underrun Counter H */
+#define URCCR 0x167 /* Underrun Counter Ctl Reg */
+#define RECNTL 0x168 /* Rx EOM Counter L */
+#define RECNTM 0x169 /* Rx EOM Counter M */
+#define RECNTH 0x16a /* Rx EOM Counter H */
+#define RECCR 0x16b /* Rx EOM Counter Ctl Reg */
+#define ORCNTL 0x16c /* Overrun Counter L */
+#define ORCNTH 0x16d /* Overrun Counter H */
+#define ORCCR 0x16f /* Overrun Counter Ctl Reg */
+#define CECNTL 0x170 /* CRC Counter L */
+#define CECNTH 0x171 /* CRC Counter H */
+#define CECCR 0x173 /* CRC Counter Ctl Reg */
+#define ABCNTL 0x174 /* Abort frame Counter L */
+#define ABCNTH 0x175 /* Abort frame Counter H */
+#define ABCCR 0x177 /* Abort frame Counter Ctl Reg */
+#define SHCNTL 0x178 /* Short frame Counter L */
+#define SHCNTH 0x179 /* Short frame Counter H */
+#define SHCCR 0x17b /* Short frame Counter Ctl Reg */
+#define RSCNTL 0x17c /* Residual bit Counter L */
+#define RSCNTH 0x17d /* Residual bit Counter H */
+#define RSCCR 0x17f /* Residual bit Counter Ctl Reg */
+
+/* Register Programming Constants */
+
+#define IR0_DMIC 0x00000001
+#define IR0_DMIB 0x00000002
+#define IR0_DMIA 0x00000004
+#define IR0_EFT 0x00000008
+#define IR0_DMAREQ 0x00010000
+#define IR0_TXINT 0x00020000
+#define IR0_RXINTB 0x00040000
+#define IR0_RXINTA 0x00080000
+#define IR0_TXRDY 0x00100000
+#define IR0_RXRDY 0x00200000
+
+#define MD0_CRC16_0 0x00
+#define MD0_CRC16_1 0x01
+#define MD0_CRC32 0x02
+#define MD0_CRC_CCITT 0x03
+#define MD0_CRCC0 0x04
+#define MD0_CRCC1 0x08
+#define MD0_AUTO_ENA 0x10
+#define MD0_ASYNC 0x00
+#define MD0_BY_MSYNC 0x20
+#define MD0_BY_BISYNC 0x40
+#define MD0_BY_EXT 0x60
+#define MD0_BIT_SYNC 0x80
+#define MD0_TRANSP 0xc0
+
+#define MD0_HDLC 0x80 /* Bit-sync HDLC mode */
+
+#define MD0_CRC_NONE 0x00
+#define MD0_CRC_16_0 0x04
+#define MD0_CRC_16 0x05
+#define MD0_CRC_ITU32 0x06
+#define MD0_CRC_ITU 0x07
+
+#define MD1_NOADDR 0x00
+#define MD1_SADDR1 0x40
+#define MD1_SADDR2 0x80
+#define MD1_DADDR 0xc0
+
+#define MD2_NRZI_IEEE 0x40
+#define MD2_MANCHESTER 0x80
+#define MD2_FM_MARK 0xA0
+#define MD2_FM_SPACE 0xC0
+#define MD2_LOOPBACK 0x03 /* Local data Loopback */
+
+#define MD2_F_DUPLEX 0x00
+#define MD2_AUTO_ECHO 0x01
+#define MD2_LOOP_HI_Z 0x02
+#define MD2_LOOP_MIR 0x03
+#define MD2_ADPLL_X8 0x00
+#define MD2_ADPLL_X16 0x08
+#define MD2_ADPLL_X32 0x10
+#define MD2_NRZ 0x00
+#define MD2_NRZI 0x20
+#define MD2_NRZ_IEEE 0x40
+#define MD2_MANCH 0x00
+#define MD2_FM1 0x20
+#define MD2_FM0 0x40
+#define MD2_FM 0x80
+
+#define CTL_RTS 0x01
+#define CTL_DTR 0x02
+#define CTL_SYN 0x04
+#define CTL_IDLC 0x10
+#define CTL_UDRNC 0x20
+#define CTL_URSKP 0x40
+#define CTL_URCT 0x80
+
+#define CTL_NORTS 0x01
+#define CTL_NODTR 0x02
+#define CTL_IDLE 0x10
+
+#define RXS_BR0 0x01
+#define RXS_BR1 0x02
+#define RXS_BR2 0x04
+#define RXS_BR3 0x08
+#define RXS_ECLK 0x00
+#define RXS_ECLK_NS 0x20
+#define RXS_IBRG 0x40
+#define RXS_PLL1 0x50
+#define RXS_PLL2 0x60
+#define RXS_PLL3 0x70
+#define RXS_DRTXC 0x80
+
+#define TXS_BR0 0x01
+#define TXS_BR1 0x02
+#define TXS_BR2 0x04
+#define TXS_BR3 0x08
+#define TXS_ECLK 0x00
+#define TXS_IBRG 0x40
+#define TXS_RCLK 0x60
+#define TXS_DTRXC 0x80
+
+#define EXS_RES0 0x01
+#define EXS_RES1 0x02
+#define EXS_RES2 0x04
+#define EXS_TES0 0x10
+#define EXS_TES1 0x20
+#define EXS_TES2 0x40
+
+#define CLK_BRG_MASK 0x0F
+#define CLK_PIN_OUT 0x80
+#define CLK_LINE 0x00 /* clock line input */
+#define CLK_BRG 0x40 /* internal baud rate generator */
+#define CLK_TX_RXCLK 0x60 /* TX clock from RX clock */
+
+#define CMD_RX_RST 0x11
+#define CMD_RX_ENA 0x12
+#define CMD_RX_DIS 0x13
+#define CMD_RX_CRC_INIT 0x14
+#define CMD_RX_MSG_REJ 0x15
+#define CMD_RX_MP_SRCH 0x16
+#define CMD_RX_CRC_EXC 0x17
+#define CMD_RX_CRC_FRC 0x18
+#define CMD_TX_RST 0x01
+#define CMD_TX_ENA 0x02
+#define CMD_TX_DISA 0x03
+#define CMD_TX_CRC_INIT 0x04
+#define CMD_TX_CRC_EXC 0x05
+#define CMD_TX_EOM 0x06
+#define CMD_TX_ABORT 0x07
+#define CMD_TX_MP_ON 0x08
+#define CMD_TX_BUF_CLR 0x09
+#define CMD_TX_DISB 0x0b
+#define CMD_CH_RST 0x21
+#define CMD_SRCH_MODE 0x31
+#define CMD_NOP 0x00
+
+#define CMD_RESET 0x21
+#define CMD_TX_ENABLE 0x02
+#define CMD_RX_ENABLE 0x12
+
+#define ST0_RXRDY 0x01
+#define ST0_TXRDY 0x02
+#define ST0_RXINTB 0x20
+#define ST0_RXINTA 0x40
+#define ST0_TXINT 0x80
+
+#define ST1_IDLE 0x01
+#define ST1_ABORT 0x02
+#define ST1_CDCD 0x04
+#define ST1_CCTS 0x08
+#define ST1_SYN_FLAG 0x10
+#define ST1_CLMD 0x20
+#define ST1_TXIDLE 0x40
+#define ST1_UDRN 0x80
+
+#define ST2_CRCE 0x04
+#define ST2_ONRN 0x08
+#define ST2_RBIT 0x10
+#define ST2_ABORT 0x20
+#define ST2_SHORT 0x40
+#define ST2_EOM 0x80
+
+#define ST3_RX_ENA 0x01
+#define ST3_TX_ENA 0x02
+#define ST3_DCD 0x04
+#define ST3_CTS 0x08
+#define ST3_SRCH_MODE 0x10
+#define ST3_SLOOP 0x20
+#define ST3_GPI 0x80
+
+#define ST4_RDNR 0x01
+#define ST4_RDCR 0x02
+#define ST4_TDNR 0x04
+#define ST4_TDCR 0x08
+#define ST4_OCLM 0x20
+#define ST4_CFT 0x40
+#define ST4_CGPI 0x80
+
+#define FST_CRCEF 0x04
+#define FST_OVRNF 0x08
+#define FST_RBIF 0x10
+#define FST_ABTF 0x20
+#define FST_SHRTF 0x40
+#define FST_EOMF 0x80
+
+#define IE0_RXRDY 0x01
+#define IE0_TXRDY 0x02
+#define IE0_RXINTB 0x20
+#define IE0_RXINTA 0x40
+#define IE0_TXINT 0x80
+#define IE0_UDRN 0x00008000 /* TX underrun MSCI interrupt enable */
+#define IE0_CDCD 0x00000400 /* CD level change interrupt enable */
+
+#define IE1_IDLD 0x01
+#define IE1_ABTD 0x02
+#define IE1_CDCD 0x04
+#define IE1_CCTS 0x08
+#define IE1_SYNCD 0x10
+#define IE1_CLMD 0x20
+#define IE1_IDL 0x40
+#define IE1_UDRN 0x80
+
+#define IE2_CRCE 0x04
+#define IE2_OVRN 0x08
+#define IE2_RBIT 0x10
+#define IE2_ABT 0x20
+#define IE2_SHRT 0x40
+#define IE2_EOM 0x80
+
+#define IE4_RDNR 0x01
+#define IE4_RDCR 0x02
+#define IE4_TDNR 0x04
+#define IE4_TDCR 0x08
+#define IE4_OCLM 0x20
+#define IE4_CFT 0x40
+#define IE4_CGPI 0x80
+
+#define FIE_CRCEF 0x04
+#define FIE_OVRNF 0x08
+#define FIE_RBIF 0x10
+#define FIE_ABTF 0x20
+#define FIE_SHRTF 0x40
+#define FIE_EOMF 0x80
+
+#define DSR_DWE 0x01
+#define DSR_DE 0x02
+#define DSR_REF 0x04
+#define DSR_UDRF 0x04
+#define DSR_COA 0x08
+#define DSR_COF 0x10
+#define DSR_BOF 0x20
+#define DSR_EOM 0x40
+#define DSR_EOT 0x80
+
+#define DIR_REF 0x04
+#define DIR_UDRF 0x04
+#define DIR_COA 0x08
+#define DIR_COF 0x10
+#define DIR_BOF 0x20
+#define DIR_EOM 0x40
+#define DIR_EOT 0x80
+
+#define DIR_REFE 0x04
+#define DIR_UDRFE 0x04
+#define DIR_COAE 0x08
+#define DIR_COFE 0x10
+#define DIR_BOFE 0x20
+#define DIR_EOME 0x40
+#define DIR_EOTE 0x80
+
+#define DMR_CNTE 0x02
+#define DMR_NF 0x04
+#define DMR_SEOME 0x08
+#define DMR_TMOD 0x10
+
+#define DMER_DME 0x80 /* DMA Master Enable */
+
+#define DCR_SW_ABT 0x01
+#define DCR_FCT_CLR 0x02
+
+#define DCR_ABORT 0x01
+#define DCR_CLEAR_EOF 0x02
+
+#define PCR_COTE 0x80
+#define PCR_PR0 0x01
+#define PCR_PR1 0x02
+#define PCR_PR2 0x04
+#define PCR_CCC 0x08
+#define PCR_BRC 0x10
+#define PCR_OSB 0x40
+#define PCR_BURST 0x80
+
+#endif /* (__HD64572_H) */
diff --git a/drivers/net/wan/hd6457x.c b/drivers/net/wan/hd6457x.c
new file mode 100644
index 00000000000..d3743321a97
--- /dev/null
+++ b/drivers/net/wan/hd6457x.c
@@ -0,0 +1,853 @@
+/*
+ * Hitachi SCA HD64570 and HD64572 common driver for Linux
+ *
+ * Copyright (C) 1998-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * Sources of information:
+ * Hitachi HD64570 SCA User's Manual
+ * Hitachi HD64572 SCA-II User's Manual
+ *
+ * We use the following SCA memory map:
+ *
+ * Packet buffer descriptor rings - starting from winbase or win0base:
+ * rx_ring_buffers * sizeof(pkt_desc) = logical channel #0 RX ring
+ * tx_ring_buffers * sizeof(pkt_desc) = logical channel #0 TX ring
+ * rx_ring_buffers * sizeof(pkt_desc) = logical channel #1 RX ring (if used)
+ * tx_ring_buffers * sizeof(pkt_desc) = logical channel #1 TX ring (if used)
+ *
+ * Packet data buffers - starting from winbase + buff_offset:
+ * rx_ring_buffers * HDLC_MAX_MRU = logical channel #0 RX buffers
+ * tx_ring_buffers * HDLC_MAX_MRU = logical channel #0 TX buffers
+ * rx_ring_buffers * HDLC_MAX_MRU = logical channel #0 RX buffers (if used)
+ * tx_ring_buffers * HDLC_MAX_MRU = logical channel #0 TX buffers (if used)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include <linux/hdlc.h>
+
+#if (!defined (__HD64570_H) && !defined (__HD64572_H)) || \
+ (defined (__HD64570_H) && defined (__HD64572_H))
+#error Either hd64570.h or hd64572.h must be included
+#endif
+
+#define get_msci(port) (phy_node(port) ? MSCI1_OFFSET : MSCI0_OFFSET)
+#define get_dmac_rx(port) (phy_node(port) ? DMAC1RX_OFFSET : DMAC0RX_OFFSET)
+#define get_dmac_tx(port) (phy_node(port) ? DMAC1TX_OFFSET : DMAC0TX_OFFSET)
+
+#define SCA_INTR_MSCI(node) (node ? 0x10 : 0x01)
+#define SCA_INTR_DMAC_RX(node) (node ? 0x20 : 0x02)
+#define SCA_INTR_DMAC_TX(node) (node ? 0x40 : 0x04)
+
+#ifdef __HD64570_H /* HD64570 */
+#define sca_outa(value, reg, card) sca_outw(value, reg, card)
+#define sca_ina(reg, card) sca_inw(reg, card)
+#define writea(value, ptr) writew(value, ptr)
+
+#else /* HD64572 */
+#define sca_outa(value, reg, card) sca_outl(value, reg, card)
+#define sca_ina(reg, card) sca_inl(reg, card)
+#define writea(value, ptr) writel(value, ptr)
+#endif
+
+static inline struct net_device *port_to_dev(port_t *port)
+{
+ return port->dev;
+}
+
+static inline int sca_intr_status(card_t *card)
+{
+ u8 result = 0;
+
+#ifdef __HD64570_H /* HD64570 */
+ u8 isr0 = sca_in(ISR0, card);
+ u8 isr1 = sca_in(ISR1, card);
+
+ if (isr1 & 0x03) result |= SCA_INTR_DMAC_RX(0);
+ if (isr1 & 0x0C) result |= SCA_INTR_DMAC_TX(0);
+ if (isr1 & 0x30) result |= SCA_INTR_DMAC_RX(1);
+ if (isr1 & 0xC0) result |= SCA_INTR_DMAC_TX(1);
+ if (isr0 & 0x0F) result |= SCA_INTR_MSCI(0);
+ if (isr0 & 0xF0) result |= SCA_INTR_MSCI(1);
+
+#else /* HD64572 */
+ u32 isr0 = sca_inl(ISR0, card);
+
+ if (isr0 & 0x0000000F) result |= SCA_INTR_DMAC_RX(0);
+ if (isr0 & 0x000000F0) result |= SCA_INTR_DMAC_TX(0);
+ if (isr0 & 0x00000F00) result |= SCA_INTR_DMAC_RX(1);
+ if (isr0 & 0x0000F000) result |= SCA_INTR_DMAC_TX(1);
+ if (isr0 & 0x003E0000) result |= SCA_INTR_MSCI(0);
+ if (isr0 & 0x3E000000) result |= SCA_INTR_MSCI(1);
+
+#endif /* HD64570 vs HD64572 */
+
+ if (!(result & SCA_INTR_DMAC_TX(0)))
+ if (sca_in(DSR_TX(0), card) & DSR_EOM)
+ result |= SCA_INTR_DMAC_TX(0);
+ if (!(result & SCA_INTR_DMAC_TX(1)))
+ if (sca_in(DSR_TX(1), card) & DSR_EOM)
+ result |= SCA_INTR_DMAC_TX(1);
+
+ return result;
+}
+
+static inline port_t* dev_to_port(struct net_device *dev)
+{
+ return dev_to_hdlc(dev)->priv;
+}
+
+static inline u16 next_desc(port_t *port, u16 desc, int transmit)
+{
+ return (desc + 1) % (transmit ? port_to_card(port)->tx_ring_buffers
+ : port_to_card(port)->rx_ring_buffers);
+}
+
+
+
+static inline u16 desc_abs_number(port_t *port, u16 desc, int transmit)
+{
+ u16 rx_buffs = port_to_card(port)->rx_ring_buffers;
+ u16 tx_buffs = port_to_card(port)->tx_ring_buffers;
+
+ desc %= (transmit ? tx_buffs : rx_buffs); // called with "X + 1" etc.
+ return log_node(port) * (rx_buffs + tx_buffs) +
+ transmit * rx_buffs + desc;
+}
+
+
+
+static inline u16 desc_offset(port_t *port, u16 desc, int transmit)
+{
+ /* Descriptor offset always fits in 16 bytes */
+ return desc_abs_number(port, desc, transmit) * sizeof(pkt_desc);
+}
+
+
+
+static inline pkt_desc __iomem *desc_address(port_t *port, u16 desc, int transmit)
+{
+#ifdef PAGE0_ALWAYS_MAPPED
+ return (pkt_desc __iomem *)(win0base(port_to_card(port))
+ + desc_offset(port, desc, transmit));
+#else
+ return (pkt_desc __iomem *)(winbase(port_to_card(port))
+ + desc_offset(port, desc, transmit));
+#endif
+}
+
+
+
+static inline u32 buffer_offset(port_t *port, u16 desc, int transmit)
+{
+ return port_to_card(port)->buff_offset +
+ desc_abs_number(port, desc, transmit) * (u32)HDLC_MAX_MRU;
+}
+
+
+
+static void sca_init_sync_port(port_t *port)
+{
+ card_t *card = port_to_card(port);
+ int transmit, i;
+
+ port->rxin = 0;
+ port->txin = 0;
+ port->txlast = 0;
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+ openwin(card, 0);
+#endif
+
+ for (transmit = 0; transmit < 2; transmit++) {
+ u16 dmac = transmit ? get_dmac_tx(port) : get_dmac_rx(port);
+ u16 buffs = transmit ? card->tx_ring_buffers
+ : card->rx_ring_buffers;
+
+ for (i = 0; i < buffs; i++) {
+ pkt_desc __iomem *desc = desc_address(port, i, transmit);
+ u16 chain_off = desc_offset(port, i + 1, transmit);
+ u32 buff_off = buffer_offset(port, i, transmit);
+
+ writea(chain_off, &desc->cp);
+ writel(buff_off, &desc->bp);
+ writew(0, &desc->len);
+ writeb(0, &desc->stat);
+ }
+
+ /* DMA disable - to halt state */
+ sca_out(0, transmit ? DSR_TX(phy_node(port)) :
+ DSR_RX(phy_node(port)), card);
+ /* software ABORT - to initial state */
+ sca_out(DCR_ABORT, transmit ? DCR_TX(phy_node(port)) :
+ DCR_RX(phy_node(port)), card);
+
+#ifdef __HD64570_H
+ sca_out(0, dmac + CPB, card); /* pointer base */
+#endif
+ /* current desc addr */
+ sca_outa(desc_offset(port, 0, transmit), dmac + CDAL, card);
+ if (!transmit)
+ sca_outa(desc_offset(port, buffs - 1, transmit),
+ dmac + EDAL, card);
+ else
+ sca_outa(desc_offset(port, 0, transmit), dmac + EDAL,
+ card);
+
+ /* clear frame end interrupt counter */
+ sca_out(DCR_CLEAR_EOF, transmit ? DCR_TX(phy_node(port)) :
+ DCR_RX(phy_node(port)), card);
+
+ if (!transmit) { /* Receive */
+ /* set buffer length */
+ sca_outw(HDLC_MAX_MRU, dmac + BFLL, card);
+ /* Chain mode, Multi-frame */
+ sca_out(0x14, DMR_RX(phy_node(port)), card);
+ sca_out(DIR_EOME | DIR_BOFE, DIR_RX(phy_node(port)),
+ card);
+ /* DMA enable */
+ sca_out(DSR_DE, DSR_RX(phy_node(port)), card);
+ } else { /* Transmit */
+ /* Chain mode, Multi-frame */
+ sca_out(0x14, DMR_TX(phy_node(port)), card);
+ /* enable underflow interrupts */
+ sca_out(DIR_BOFE, DIR_TX(phy_node(port)), card);
+ }
+ }
+
+ hdlc_set_carrier(!(sca_in(get_msci(port) + ST3, card) & ST3_DCD),
+ port_to_dev(port));
+}
+
+
+
+#ifdef NEED_SCA_MSCI_INTR
+/* MSCI interrupt service */
+static inline void sca_msci_intr(port_t *port)
+{
+ u16 msci = get_msci(port);
+ card_t* card = port_to_card(port);
+ u8 stat = sca_in(msci + ST1, card); /* read MSCI ST1 status */
+
+ /* Reset MSCI TX underrun and CDCD status bit */
+ sca_out(stat & (ST1_UDRN | ST1_CDCD), msci + ST1, card);
+
+ if (stat & ST1_UDRN) {
+ struct net_device_stats *stats = hdlc_stats(port_to_dev(port));
+ stats->tx_errors++; /* TX Underrun error detected */
+ stats->tx_fifo_errors++;
+ }
+
+ if (stat & ST1_CDCD)
+ hdlc_set_carrier(!(sca_in(msci + ST3, card) & ST3_DCD),
+ port_to_dev(port));
+}
+#endif
+
+
+
+static inline void sca_rx(card_t *card, port_t *port, pkt_desc __iomem *desc, u16 rxin)
+{
+ struct net_device *dev = port_to_dev(port);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ struct sk_buff *skb;
+ u16 len;
+ u32 buff;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ u32 maxlen;
+ u8 page;
+#endif
+
+ len = readw(&desc->len);
+ skb = dev_alloc_skb(len);
+ if (!skb) {
+ stats->rx_dropped++;
+ return;
+ }
+
+ buff = buffer_offset(port, rxin, 0);
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ page = buff / winsize(card);
+ buff = buff % winsize(card);
+ maxlen = winsize(card) - buff;
+
+ openwin(card, page);
+
+ if (len > maxlen) {
+ memcpy_fromio(skb->data, winbase(card) + buff, maxlen);
+ openwin(card, page + 1);
+ memcpy_fromio(skb->data + maxlen, winbase(card), len - maxlen);
+ } else
+#endif
+ memcpy_fromio(skb->data, winbase(card) + buff, len);
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+ /* select pkt_desc table page back */
+ openwin(card, 0);
+#endif
+ skb_put(skb, len);
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s RX(%i):", dev->name, skb->len);
+ debug_frame(skb);
+#endif
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+ dev->last_rx = jiffies;
+ skb->protocol = hdlc_type_trans(skb, dev);
+ netif_rx(skb);
+}
+
+
+
+/* Receive DMA interrupt service */
+static inline void sca_rx_intr(port_t *port)
+{
+ u16 dmac = get_dmac_rx(port);
+ card_t *card = port_to_card(port);
+ u8 stat = sca_in(DSR_RX(phy_node(port)), card); /* read DMA Status */
+ struct net_device_stats *stats = hdlc_stats(port_to_dev(port));
+
+ /* Reset DSR status bits */
+ sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
+ DSR_RX(phy_node(port)), card);
+
+ if (stat & DSR_BOF)
+ stats->rx_over_errors++; /* Dropped one or more frames */
+
+ while (1) {
+ u32 desc_off = desc_offset(port, port->rxin, 0);
+ pkt_desc __iomem *desc;
+ u32 cda = sca_ina(dmac + CDAL, card);
+
+ if ((cda >= desc_off) && (cda < desc_off + sizeof(pkt_desc)))
+ break; /* No frame received */
+
+ desc = desc_address(port, port->rxin, 0);
+ stat = readb(&desc->stat);
+ if (!(stat & ST_RX_EOM))
+ port->rxpart = 1; /* partial frame received */
+ else if ((stat & ST_ERROR_MASK) || port->rxpart) {
+ stats->rx_errors++;
+ if (stat & ST_RX_OVERRUN) stats->rx_fifo_errors++;
+ else if ((stat & (ST_RX_SHORT | ST_RX_ABORT |
+ ST_RX_RESBIT)) || port->rxpart)
+ stats->rx_frame_errors++;
+ else if (stat & ST_RX_CRC) stats->rx_crc_errors++;
+ if (stat & ST_RX_EOM)
+ port->rxpart = 0; /* received last fragment */
+ } else
+ sca_rx(card, port, desc, port->rxin);
+
+ /* Set new error descriptor address */
+ sca_outa(desc_off, dmac + EDAL, card);
+ port->rxin = next_desc(port, port->rxin, 0);
+ }
+
+ /* make sure RX DMA is enabled */
+ sca_out(DSR_DE, DSR_RX(phy_node(port)), card);
+}
+
+
+
+/* Transmit DMA interrupt service */
+static inline void sca_tx_intr(port_t *port)
+{
+ struct net_device *dev = port_to_dev(port);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ u16 dmac = get_dmac_tx(port);
+ card_t* card = port_to_card(port);
+ u8 stat;
+
+ spin_lock(&port->lock);
+
+ stat = sca_in(DSR_TX(phy_node(port)), card); /* read DMA Status */
+
+ /* Reset DSR status bits */
+ sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE,
+ DSR_TX(phy_node(port)), card);
+
+ while (1) {
+ pkt_desc __iomem *desc;
+
+ u32 desc_off = desc_offset(port, port->txlast, 1);
+ u32 cda = sca_ina(dmac + CDAL, card);
+ if ((cda >= desc_off) && (cda < desc_off + sizeof(pkt_desc)))
+ break; /* Transmitter is/will_be sending this frame */
+
+ desc = desc_address(port, port->txlast, 1);
+ stats->tx_packets++;
+ stats->tx_bytes += readw(&desc->len);
+ writeb(0, &desc->stat); /* Free descriptor */
+ port->txlast = next_desc(port, port->txlast, 1);
+ }
+
+ netif_wake_queue(dev);
+ spin_unlock(&port->lock);
+}
+
+
+
+static irqreturn_t sca_intr(int irq, void* dev_id, struct pt_regs *regs)
+{
+ card_t *card = dev_id;
+ int i;
+ u8 stat;
+ int handled = 0;
+
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ u8 page = sca_get_page(card);
+#endif
+
+ while((stat = sca_intr_status(card)) != 0) {
+ handled = 1;
+ for (i = 0; i < 2; i++) {
+ port_t *port = get_port(card, i);
+ if (port) {
+ if (stat & SCA_INTR_MSCI(i))
+ sca_msci_intr(port);
+
+ if (stat & SCA_INTR_DMAC_RX(i))
+ sca_rx_intr(port);
+
+ if (stat & SCA_INTR_DMAC_TX(i))
+ sca_tx_intr(port);
+ }
+ }
+ }
+
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ openwin(card, page); /* Restore original page */
+#endif
+ return IRQ_RETVAL(handled);
+}
+
+
+
+static void sca_set_port(port_t *port)
+{
+ card_t* card = port_to_card(port);
+ u16 msci = get_msci(port);
+ u8 md2 = sca_in(msci + MD2, card);
+ unsigned int tmc, br = 10, brv = 1024;
+
+
+ if (port->settings.clock_rate > 0) {
+ /* Try lower br for better accuracy*/
+ do {
+ br--;
+ brv >>= 1; /* brv = 2^9 = 512 max in specs */
+
+ /* Baud Rate = CLOCK_BASE / TMC / 2^BR */
+ tmc = CLOCK_BASE / brv / port->settings.clock_rate;
+ }while (br > 1 && tmc <= 128);
+
+ if (tmc < 1) {
+ tmc = 1;
+ br = 0; /* For baud=CLOCK_BASE we use tmc=1 br=0 */
+ brv = 1;
+ } else if (tmc > 255)
+ tmc = 256; /* tmc=0 means 256 - low baud rates */
+
+ port->settings.clock_rate = CLOCK_BASE / brv / tmc;
+ } else {
+ br = 9; /* Minimum clock rate */
+ tmc = 256; /* 8bit = 0 */
+ port->settings.clock_rate = CLOCK_BASE / (256 * 512);
+ }
+
+ port->rxs = (port->rxs & ~CLK_BRG_MASK) | br;
+ port->txs = (port->txs & ~CLK_BRG_MASK) | br;
+ port->tmc = tmc;
+
+ /* baud divisor - time constant*/
+#ifdef __HD64570_H
+ sca_out(port->tmc, msci + TMC, card);
+#else
+ sca_out(port->tmc, msci + TMCR, card);
+ sca_out(port->tmc, msci + TMCT, card);
+#endif
+
+ /* Set BRG bits */
+ sca_out(port->rxs, msci + RXS, card);
+ sca_out(port->txs, msci + TXS, card);
+
+ if (port->settings.loopback)
+ md2 |= MD2_LOOPBACK;
+ else
+ md2 &= ~MD2_LOOPBACK;
+
+ sca_out(md2, msci + MD2, card);
+
+}
+
+
+
+static void sca_open(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ card_t* card = port_to_card(port);
+ u16 msci = get_msci(port);
+ u8 md0, md2;
+
+ switch(port->encoding) {
+ case ENCODING_NRZ: md2 = MD2_NRZ; break;
+ case ENCODING_NRZI: md2 = MD2_NRZI; break;
+ case ENCODING_FM_MARK: md2 = MD2_FM_MARK; break;
+ case ENCODING_FM_SPACE: md2 = MD2_FM_SPACE; break;
+ default: md2 = MD2_MANCHESTER;
+ }
+
+ if (port->settings.loopback)
+ md2 |= MD2_LOOPBACK;
+
+ switch(port->parity) {
+ case PARITY_CRC16_PR0: md0 = MD0_HDLC | MD0_CRC_16_0; break;
+ case PARITY_CRC16_PR1: md0 = MD0_HDLC | MD0_CRC_16; break;
+#ifdef __HD64570_H
+ case PARITY_CRC16_PR0_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU_0; break;
+#else
+ case PARITY_CRC32_PR1_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU32; break;
+#endif
+ case PARITY_CRC16_PR1_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU; break;
+ default: md0 = MD0_HDLC | MD0_CRC_NONE;
+ }
+
+ sca_out(CMD_RESET, msci + CMD, card);
+ sca_out(md0, msci + MD0, card);
+ sca_out(0x00, msci + MD1, card); /* no address field check */
+ sca_out(md2, msci + MD2, card);
+ sca_out(0x7E, msci + IDL, card); /* flag character 0x7E */
+#ifdef __HD64570_H
+ sca_out(CTL_IDLE, msci + CTL, card);
+#else
+ /* Skip the rest of underrun frame */
+ sca_out(CTL_IDLE | CTL_URCT | CTL_URSKP, msci + CTL, card);
+#endif
+
+#ifdef __HD64570_H
+ /* Allow at least 8 bytes before requesting RX DMA operation */
+ /* TX with higher priority and possibly with shorter transfers */
+ sca_out(0x07, msci + RRC, card); /* +1=RXRDY/DMA activation condition*/
+ sca_out(0x10, msci + TRC0, card); /* = TXRDY/DMA activation condition*/
+ sca_out(0x14, msci + TRC1, card); /* +1=TXRDY/DMA deactiv condition */
+#else
+ sca_out(0x0F, msci + RNR, card); /* +1=RX DMA activation condition */
+ sca_out(0x3C, msci + TFS, card); /* +1 = TX start */
+ sca_out(0x38, msci + TCR, card); /* =Critical TX DMA activ condition */
+ sca_out(0x38, msci + TNR0, card); /* =TX DMA activation condition */
+ sca_out(0x3F, msci + TNR1, card); /* +1=TX DMA deactivation condition*/
+#endif
+
+/* We're using the following interrupts:
+ - TXINT (DMAC completed all transmisions, underrun or DCD change)
+ - all DMA interrupts
+*/
+
+ hdlc_set_carrier(!(sca_in(msci + ST3, card) & ST3_DCD), dev);
+
+#ifdef __HD64570_H
+ /* MSCI TX INT and RX INT A IRQ enable */
+ sca_out(IE0_TXINT | IE0_RXINTA, msci + IE0, card);
+ sca_out(IE1_UDRN | IE1_CDCD, msci + IE1, card);
+ sca_out(sca_in(IER0, card) | (phy_node(port) ? 0xC0 : 0x0C),
+ IER0, card); /* TXINT and RXINT */
+ /* enable DMA IRQ */
+ sca_out(sca_in(IER1, card) | (phy_node(port) ? 0xF0 : 0x0F),
+ IER1, card);
+#else
+ /* MSCI TXINT and RXINTA interrupt enable */
+ sca_outl(IE0_TXINT | IE0_RXINTA | IE0_UDRN | IE0_CDCD, msci + IE0,
+ card);
+ /* DMA & MSCI IRQ enable */
+ sca_outl(sca_inl(IER0, card) |
+ (phy_node(port) ? 0x0A006600 : 0x000A0066), IER0, card);
+#endif
+
+#ifdef __HD64570_H
+ sca_out(port->tmc, msci + TMC, card); /* Restore registers */
+#else
+ sca_out(port->tmc, msci + TMCR, card);
+ sca_out(port->tmc, msci + TMCT, card);
+#endif
+ sca_out(port->rxs, msci + RXS, card);
+ sca_out(port->txs, msci + TXS, card);
+ sca_out(CMD_TX_ENABLE, msci + CMD, card);
+ sca_out(CMD_RX_ENABLE, msci + CMD, card);
+
+ netif_start_queue(dev);
+}
+
+
+
+static void sca_close(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ card_t* card = port_to_card(port);
+
+ /* reset channel */
+ sca_out(CMD_RESET, get_msci(port) + CMD, port_to_card(port));
+#ifdef __HD64570_H
+ /* disable MSCI interrupts */
+ sca_out(sca_in(IER0, card) & (phy_node(port) ? 0x0F : 0xF0),
+ IER0, card);
+ /* disable DMA interrupts */
+ sca_out(sca_in(IER1, card) & (phy_node(port) ? 0x0F : 0xF0),
+ IER1, card);
+#else
+ /* disable DMA & MSCI IRQ */
+ sca_outl(sca_inl(IER0, card) &
+ (phy_node(port) ? 0x00FF00FF : 0xFF00FF00), IER0, card);
+#endif
+ netif_stop_queue(dev);
+}
+
+
+
+static int sca_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ if (encoding != ENCODING_NRZ &&
+ encoding != ENCODING_NRZI &&
+ encoding != ENCODING_FM_MARK &&
+ encoding != ENCODING_FM_SPACE &&
+ encoding != ENCODING_MANCHESTER)
+ return -EINVAL;
+
+ if (parity != PARITY_NONE &&
+ parity != PARITY_CRC16_PR0 &&
+ parity != PARITY_CRC16_PR1 &&
+#ifdef __HD64570_H
+ parity != PARITY_CRC16_PR0_CCITT &&
+#else
+ parity != PARITY_CRC32_PR1_CCITT &&
+#endif
+ parity != PARITY_CRC16_PR1_CCITT)
+ return -EINVAL;
+
+ dev_to_port(dev)->encoding = encoding;
+ dev_to_port(dev)->parity = parity;
+ return 0;
+}
+
+
+
+#ifdef DEBUG_RINGS
+static void sca_dump_rings(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ card_t *card = port_to_card(port);
+ u16 cnt;
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+ u8 page;
+#endif
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+ page = sca_get_page(card);
+ openwin(card, 0);
+#endif
+
+ printk(KERN_DEBUG "RX ring: CDA=%u EDA=%u DSR=%02X in=%u %sactive",
+ sca_ina(get_dmac_rx(port) + CDAL, card),
+ sca_ina(get_dmac_rx(port) + EDAL, card),
+ sca_in(DSR_RX(phy_node(port)), card), port->rxin,
+ sca_in(DSR_RX(phy_node(port)), card) & DSR_DE?"":"in");
+ for (cnt = 0; cnt < port_to_card(port)->rx_ring_buffers; cnt++)
+ printk(" %02X", readb(&(desc_address(port, cnt, 0)->stat)));
+
+ printk("\n" KERN_DEBUG "TX ring: CDA=%u EDA=%u DSR=%02X in=%u "
+ "last=%u %sactive",
+ sca_ina(get_dmac_tx(port) + CDAL, card),
+ sca_ina(get_dmac_tx(port) + EDAL, card),
+ sca_in(DSR_TX(phy_node(port)), card), port->txin, port->txlast,
+ sca_in(DSR_TX(phy_node(port)), card) & DSR_DE ? "" : "in");
+
+ for (cnt = 0; cnt < port_to_card(port)->tx_ring_buffers; cnt++)
+ printk(" %02X", readb(&(desc_address(port, cnt, 1)->stat)));
+ printk("\n");
+
+ printk(KERN_DEBUG "MSCI: MD: %02x %02x %02x, "
+ "ST: %02x %02x %02x %02x"
+#ifdef __HD64572_H
+ " %02x"
+#endif
+ ", FST: %02x CST: %02x %02x\n",
+ sca_in(get_msci(port) + MD0, card),
+ sca_in(get_msci(port) + MD1, card),
+ sca_in(get_msci(port) + MD2, card),
+ sca_in(get_msci(port) + ST0, card),
+ sca_in(get_msci(port) + ST1, card),
+ sca_in(get_msci(port) + ST2, card),
+ sca_in(get_msci(port) + ST3, card),
+#ifdef __HD64572_H
+ sca_in(get_msci(port) + ST4, card),
+#endif
+ sca_in(get_msci(port) + FST, card),
+ sca_in(get_msci(port) + CST0, card),
+ sca_in(get_msci(port) + CST1, card));
+
+#ifdef __HD64572_H
+ printk(KERN_DEBUG "ILAR: %02x ISR: %08x %08x\n", sca_in(ILAR, card),
+ sca_inl(ISR0, card), sca_inl(ISR1, card));
+#else
+ printk(KERN_DEBUG "ISR: %02x %02x %02x\n", sca_in(ISR0, card),
+ sca_in(ISR1, card), sca_in(ISR2, card));
+#endif
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+ openwin(card, page); /* Restore original page */
+#endif
+}
+#endif /* DEBUG_RINGS */
+
+
+
+static int sca_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ card_t *card = port_to_card(port);
+ pkt_desc __iomem *desc;
+ u32 buff, len;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ u8 page;
+ u32 maxlen;
+#endif
+
+ spin_lock_irq(&port->lock);
+
+ desc = desc_address(port, port->txin + 1, 1);
+ if (readb(&desc->stat)) { /* allow 1 packet gap */
+ /* should never happen - previous xmit should stop queue */
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
+#endif
+ netif_stop_queue(dev);
+ spin_unlock_irq(&port->lock);
+ return 1; /* request packet to be queued */
+ }
+
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
+ debug_frame(skb);
+#endif
+
+ desc = desc_address(port, port->txin, 1);
+ buff = buffer_offset(port, port->txin, 1);
+ len = skb->len;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ page = buff / winsize(card);
+ buff = buff % winsize(card);
+ maxlen = winsize(card) - buff;
+
+ openwin(card, page);
+ if (len > maxlen) {
+ memcpy_toio(winbase(card) + buff, skb->data, maxlen);
+ openwin(card, page + 1);
+ memcpy_toio(winbase(card), skb->data + maxlen, len - maxlen);
+ }
+ else
+#endif
+ memcpy_toio(winbase(card) + buff, skb->data, len);
+
+#if !defined(PAGE0_ALWAYS_MAPPED) && !defined(ALL_PAGES_ALWAYS_MAPPED)
+ openwin(card, 0); /* select pkt_desc table page back */
+#endif
+ writew(len, &desc->len);
+ writeb(ST_TX_EOM, &desc->stat);
+ dev->trans_start = jiffies;
+
+ port->txin = next_desc(port, port->txin, 1);
+ sca_outa(desc_offset(port, port->txin, 1),
+ get_dmac_tx(port) + EDAL, card);
+
+ sca_out(DSR_DE, DSR_TX(phy_node(port)), card); /* Enable TX DMA */
+
+ desc = desc_address(port, port->txin + 1, 1);
+ if (readb(&desc->stat)) /* allow 1 packet gap */
+ netif_stop_queue(dev);
+
+ spin_unlock_irq(&port->lock);
+
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+
+
+#ifdef NEED_DETECT_RAM
+static u32 __devinit sca_detect_ram(card_t *card, u8 __iomem *rambase, u32 ramsize)
+{
+ /* Round RAM size to 32 bits, fill from end to start */
+ u32 i = ramsize &= ~3;
+
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ u32 size = winsize(card);
+
+ openwin(card, (i - 4) / size); /* select last window */
+#endif
+ do {
+ i -= 4;
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ if ((i + 4) % size == 0)
+ openwin(card, i / size);
+ writel(i ^ 0x12345678, rambase + i % size);
+#else
+ writel(i ^ 0x12345678, rambase + i);
+#endif
+ }while (i > 0);
+
+ for (i = 0; i < ramsize ; i += 4) {
+#ifndef ALL_PAGES_ALWAYS_MAPPED
+ if (i % size == 0)
+ openwin(card, i / size);
+
+ if (readl(rambase + i % size) != (i ^ 0x12345678))
+ break;
+#else
+ if (readl(rambase + i) != (i ^ 0x12345678))
+ break;
+#endif
+ }
+
+ return i;
+}
+#endif /* NEED_DETECT_RAM */
+
+
+
+static void __devinit sca_init(card_t *card, int wait_states)
+{
+ sca_out(wait_states, WCRL, card); /* Wait Control */
+ sca_out(wait_states, WCRM, card);
+ sca_out(wait_states, WCRH, card);
+
+ sca_out(0, DMER, card); /* DMA Master disable */
+ sca_out(0x03, PCR, card); /* DMA priority */
+ sca_out(0, DSR_RX(0), card); /* DMA disable - to halt state */
+ sca_out(0, DSR_TX(0), card);
+ sca_out(0, DSR_RX(1), card);
+ sca_out(0, DSR_TX(1), card);
+ sca_out(DMER_DME, DMER, card); /* DMA Master enable */
+}
diff --git a/drivers/net/wan/hdlc_cisco.c b/drivers/net/wan/hdlc_cisco.c
new file mode 100644
index 00000000000..c1b6896d700
--- /dev/null
+++ b/drivers/net/wan/hdlc_cisco.c
@@ -0,0 +1,330 @@
+/*
+ * Generic HDLC support routines for Linux
+ * Cisco HDLC support
+ *
+ * Copyright (C) 2000 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+#undef DEBUG_HARD_HEADER
+
+#define CISCO_MULTICAST 0x8F /* Cisco multicast address */
+#define CISCO_UNICAST 0x0F /* Cisco unicast address */
+#define CISCO_KEEPALIVE 0x8035 /* Cisco keepalive protocol */
+#define CISCO_SYS_INFO 0x2000 /* Cisco interface/system info */
+#define CISCO_ADDR_REQ 0 /* Cisco address request */
+#define CISCO_ADDR_REPLY 1 /* Cisco address reply */
+#define CISCO_KEEPALIVE_REQ 2 /* Cisco keepalive request */
+
+
+static int cisco_hard_header(struct sk_buff *skb, struct net_device *dev,
+ u16 type, void *daddr, void *saddr,
+ unsigned int len)
+{
+ hdlc_header *data;
+#ifdef DEBUG_HARD_HEADER
+ printk(KERN_DEBUG "%s: cisco_hard_header called\n", dev->name);
+#endif
+
+ skb_push(skb, sizeof(hdlc_header));
+ data = (hdlc_header*)skb->data;
+ if (type == CISCO_KEEPALIVE)
+ data->address = CISCO_MULTICAST;
+ else
+ data->address = CISCO_UNICAST;
+ data->control = 0;
+ data->protocol = htons(type);
+
+ return sizeof(hdlc_header);
+}
+
+
+
+static void cisco_keepalive_send(struct net_device *dev, u32 type,
+ u32 par1, u32 par2)
+{
+ struct sk_buff *skb;
+ cisco_packet *data;
+
+ skb = dev_alloc_skb(sizeof(hdlc_header) + sizeof(cisco_packet));
+ if (!skb) {
+ printk(KERN_WARNING
+ "%s: Memory squeeze on cisco_keepalive_send()\n",
+ dev->name);
+ return;
+ }
+ skb_reserve(skb, 4);
+ cisco_hard_header(skb, dev, CISCO_KEEPALIVE, NULL, NULL, 0);
+ data = (cisco_packet*)skb->tail;
+
+ data->type = htonl(type);
+ data->par1 = htonl(par1);
+ data->par2 = htonl(par2);
+ data->rel = 0xFFFF;
+ /* we will need do_div here if 1000 % HZ != 0 */
+ data->time = htonl((jiffies - INITIAL_JIFFIES) * (1000 / HZ));
+
+ skb_put(skb, sizeof(cisco_packet));
+ skb->priority = TC_PRIO_CONTROL;
+ skb->dev = dev;
+ skb->nh.raw = skb->data;
+
+ dev_queue_xmit(skb);
+}
+
+
+
+static unsigned short cisco_type_trans(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ hdlc_header *data = (hdlc_header*)skb->data;
+
+ if (skb->len < sizeof(hdlc_header))
+ return __constant_htons(ETH_P_HDLC);
+
+ if (data->address != CISCO_MULTICAST &&
+ data->address != CISCO_UNICAST)
+ return __constant_htons(ETH_P_HDLC);
+
+ switch(data->protocol) {
+ case __constant_htons(ETH_P_IP):
+ case __constant_htons(ETH_P_IPX):
+ case __constant_htons(ETH_P_IPV6):
+ skb_pull(skb, sizeof(hdlc_header));
+ return data->protocol;
+ default:
+ return __constant_htons(ETH_P_HDLC);
+ }
+}
+
+
+static int cisco_rx(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ hdlc_header *data = (hdlc_header*)skb->data;
+ cisco_packet *cisco_data;
+ struct in_device *in_dev;
+ u32 addr, mask;
+
+ if (skb->len < sizeof(hdlc_header))
+ goto rx_error;
+
+ if (data->address != CISCO_MULTICAST &&
+ data->address != CISCO_UNICAST)
+ goto rx_error;
+
+ switch(ntohs(data->protocol)) {
+ case CISCO_SYS_INFO:
+ /* Packet is not needed, drop it. */
+ dev_kfree_skb_any(skb);
+ return NET_RX_SUCCESS;
+
+ case CISCO_KEEPALIVE:
+ if (skb->len != sizeof(hdlc_header) + CISCO_PACKET_LEN &&
+ skb->len != sizeof(hdlc_header) + CISCO_BIG_PACKET_LEN) {
+ printk(KERN_INFO "%s: Invalid length of Cisco "
+ "control packet (%d bytes)\n",
+ dev->name, skb->len);
+ goto rx_error;
+ }
+
+ cisco_data = (cisco_packet*)(skb->data + sizeof(hdlc_header));
+
+ switch(ntohl (cisco_data->type)) {
+ case CISCO_ADDR_REQ: /* Stolen from syncppp.c :-) */
+ in_dev = dev->ip_ptr;
+ addr = 0;
+ mask = ~0; /* is the mask correct? */
+
+ if (in_dev != NULL) {
+ struct in_ifaddr **ifap = &in_dev->ifa_list;
+
+ while (*ifap != NULL) {
+ if (strcmp(dev->name,
+ (*ifap)->ifa_label) == 0) {
+ addr = (*ifap)->ifa_local;
+ mask = (*ifap)->ifa_mask;
+ break;
+ }
+ ifap = &(*ifap)->ifa_next;
+ }
+
+ cisco_keepalive_send(dev, CISCO_ADDR_REPLY,
+ addr, mask);
+ }
+ dev_kfree_skb_any(skb);
+ return NET_RX_SUCCESS;
+
+ case CISCO_ADDR_REPLY:
+ printk(KERN_INFO "%s: Unexpected Cisco IP address "
+ "reply\n", dev->name);
+ goto rx_error;
+
+ case CISCO_KEEPALIVE_REQ:
+ hdlc->state.cisco.rxseq = ntohl(cisco_data->par1);
+ if (hdlc->state.cisco.request_sent &&
+ ntohl(cisco_data->par2)==hdlc->state.cisco.txseq) {
+ hdlc->state.cisco.last_poll = jiffies;
+ if (!hdlc->state.cisco.up) {
+ u32 sec, min, hrs, days;
+ sec = ntohl(cisco_data->time) / 1000;
+ min = sec / 60; sec -= min * 60;
+ hrs = min / 60; min -= hrs * 60;
+ days = hrs / 24; hrs -= days * 24;
+ printk(KERN_INFO "%s: Link up (peer "
+ "uptime %ud%uh%um%us)\n",
+ dev->name, days, hrs,
+ min, sec);
+ netif_carrier_on(dev);
+ hdlc->state.cisco.up = 1;
+ }
+ }
+
+ dev_kfree_skb_any(skb);
+ return NET_RX_SUCCESS;
+ } /* switch(keepalive type) */
+ } /* switch(protocol) */
+
+ printk(KERN_INFO "%s: Unsupported protocol %x\n", dev->name,
+ data->protocol);
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+
+ rx_error:
+ hdlc->stats.rx_errors++; /* Mark error */
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+}
+
+
+
+static void cisco_timer(unsigned long arg)
+{
+ struct net_device *dev = (struct net_device *)arg;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ if (hdlc->state.cisco.up &&
+ time_after(jiffies, hdlc->state.cisco.last_poll +
+ hdlc->state.cisco.settings.timeout * HZ)) {
+ hdlc->state.cisco.up = 0;
+ printk(KERN_INFO "%s: Link down\n", dev->name);
+ netif_carrier_off(dev);
+ }
+
+ cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ,
+ ++hdlc->state.cisco.txseq,
+ hdlc->state.cisco.rxseq);
+ hdlc->state.cisco.request_sent = 1;
+ hdlc->state.cisco.timer.expires = jiffies +
+ hdlc->state.cisco.settings.interval * HZ;
+ hdlc->state.cisco.timer.function = cisco_timer;
+ hdlc->state.cisco.timer.data = arg;
+ add_timer(&hdlc->state.cisco.timer);
+}
+
+
+
+static void cisco_start(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ hdlc->state.cisco.up = 0;
+ hdlc->state.cisco.request_sent = 0;
+ hdlc->state.cisco.txseq = hdlc->state.cisco.rxseq = 0;
+
+ init_timer(&hdlc->state.cisco.timer);
+ hdlc->state.cisco.timer.expires = jiffies + HZ; /*First poll after 1s*/
+ hdlc->state.cisco.timer.function = cisco_timer;
+ hdlc->state.cisco.timer.data = (unsigned long)dev;
+ add_timer(&hdlc->state.cisco.timer);
+}
+
+
+
+static void cisco_stop(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ del_timer_sync(&hdlc->state.cisco.timer);
+ if (netif_carrier_ok(dev))
+ netif_carrier_off(dev);
+ hdlc->state.cisco.up = 0;
+ hdlc->state.cisco.request_sent = 0;
+}
+
+
+
+int hdlc_cisco_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+ cisco_proto __user *cisco_s = ifr->ifr_settings.ifs_ifsu.cisco;
+ const size_t size = sizeof(cisco_proto);
+ cisco_proto new_settings;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int result;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_PROTO:
+ ifr->ifr_settings.type = IF_PROTO_CISCO;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(cisco_s, &hdlc->state.cisco.settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_PROTO_CISCO:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if(dev->flags & IFF_UP)
+ return -EBUSY;
+
+ if (copy_from_user(&new_settings, cisco_s, size))
+ return -EFAULT;
+
+ if (new_settings.interval < 1 ||
+ new_settings.timeout < 2)
+ return -EINVAL;
+
+ result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+
+ if (result)
+ return result;
+
+ hdlc_proto_detach(hdlc);
+ memcpy(&hdlc->state.cisco.settings, &new_settings, size);
+ memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+ hdlc->proto.start = cisco_start;
+ hdlc->proto.stop = cisco_stop;
+ hdlc->proto.netif_rx = cisco_rx;
+ hdlc->proto.type_trans = cisco_type_trans;
+ hdlc->proto.id = IF_PROTO_CISCO;
+ dev->hard_start_xmit = hdlc->xmit;
+ dev->hard_header = cisco_hard_header;
+ dev->hard_header_cache = NULL;
+ dev->type = ARPHRD_CISCO;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->addr_len = 0;
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_fr.c b/drivers/net/wan/hdlc_fr.c
new file mode 100644
index 00000000000..7f450b51a6c
--- /dev/null
+++ b/drivers/net/wan/hdlc_fr.c
@@ -0,0 +1,1237 @@
+/*
+ * Generic HDLC support routines for Linux
+ * Frame Relay support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+
+ Theory of PVC state
+
+ DCE mode:
+
+ (exist,new) -> 0,0 when "PVC create" or if "link unreliable"
+ 0,x -> 1,1 if "link reliable" when sending FULL STATUS
+ 1,1 -> 1,0 if received FULL STATUS ACK
+
+ (active) -> 0 when "ifconfig PVC down" or "link unreliable" or "PVC create"
+ -> 1 when "PVC up" and (exist,new) = 1,0
+
+ DTE mode:
+ (exist,new,active) = FULL STATUS if "link reliable"
+ = 0, 0, 0 if "link unreliable"
+ No LMI:
+ active = open and "link reliable"
+ exist = new = not used
+
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/random.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <linux/hdlc.h>
+
+#undef DEBUG_PKT
+#undef DEBUG_ECN
+#undef DEBUG_LINK
+
+#define MAXLEN_LMISTAT 20 /* max size of status enquiry frame */
+
+#define PVC_STATE_NEW 0x01
+#define PVC_STATE_ACTIVE 0x02
+#define PVC_STATE_FECN 0x08 /* FECN condition */
+#define PVC_STATE_BECN 0x10 /* BECN condition */
+
+
+#define FR_UI 0x03
+#define FR_PAD 0x00
+
+#define NLPID_IP 0xCC
+#define NLPID_IPV6 0x8E
+#define NLPID_SNAP 0x80
+#define NLPID_PAD 0x00
+#define NLPID_Q933 0x08
+
+
+#define LMI_DLCI 0 /* LMI DLCI */
+#define LMI_PROTO 0x08
+#define LMI_CALLREF 0x00 /* Call Reference */
+#define LMI_ANSI_LOCKSHIFT 0x95 /* ANSI lockshift */
+#define LMI_REPTYPE 1 /* report type */
+#define LMI_CCITT_REPTYPE 0x51
+#define LMI_ALIVE 3 /* keep alive */
+#define LMI_CCITT_ALIVE 0x53
+#define LMI_PVCSTAT 7 /* pvc status */
+#define LMI_CCITT_PVCSTAT 0x57
+#define LMI_FULLREP 0 /* full report */
+#define LMI_INTEGRITY 1 /* link integrity report */
+#define LMI_SINGLE 2 /* single pvc report */
+#define LMI_STATUS_ENQUIRY 0x75
+#define LMI_STATUS 0x7D /* reply */
+
+#define LMI_REPT_LEN 1 /* report type element length */
+#define LMI_INTEG_LEN 2 /* link integrity element length */
+
+#define LMI_LENGTH 13 /* standard LMI frame length */
+#define LMI_ANSI_LENGTH 14
+
+
+typedef struct {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ unsigned ea1: 1;
+ unsigned cr: 1;
+ unsigned dlcih: 6;
+
+ unsigned ea2: 1;
+ unsigned de: 1;
+ unsigned becn: 1;
+ unsigned fecn: 1;
+ unsigned dlcil: 4;
+#else
+ unsigned dlcih: 6;
+ unsigned cr: 1;
+ unsigned ea1: 1;
+
+ unsigned dlcil: 4;
+ unsigned fecn: 1;
+ unsigned becn: 1;
+ unsigned de: 1;
+ unsigned ea2: 1;
+#endif
+}__attribute__ ((packed)) fr_hdr;
+
+
+static inline u16 q922_to_dlci(u8 *hdr)
+{
+ return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);
+}
+
+
+
+static inline void dlci_to_q922(u8 *hdr, u16 dlci)
+{
+ hdr[0] = (dlci >> 2) & 0xFC;
+ hdr[1] = ((dlci << 4) & 0xF0) | 0x01;
+}
+
+
+
+static inline pvc_device* find_pvc(hdlc_device *hdlc, u16 dlci)
+{
+ pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+ while (pvc) {
+ if (pvc->dlci == dlci)
+ return pvc;
+ if (pvc->dlci > dlci)
+ return NULL; /* the listed is sorted */
+ pvc = pvc->next;
+ }
+
+ return NULL;
+}
+
+
+static inline pvc_device* add_pvc(struct net_device *dev, u16 dlci)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ pvc_device *pvc, **pvc_p = &hdlc->state.fr.first_pvc;
+
+ while (*pvc_p) {
+ if ((*pvc_p)->dlci == dlci)
+ return *pvc_p;
+ if ((*pvc_p)->dlci > dlci)
+ break; /* the list is sorted */
+ pvc_p = &(*pvc_p)->next;
+ }
+
+ pvc = kmalloc(sizeof(pvc_device), GFP_ATOMIC);
+ if (!pvc)
+ return NULL;
+
+ memset(pvc, 0, sizeof(pvc_device));
+ pvc->dlci = dlci;
+ pvc->master = dev;
+ pvc->next = *pvc_p; /* Put it in the chain */
+ *pvc_p = pvc;
+ return pvc;
+}
+
+
+static inline int pvc_is_used(pvc_device *pvc)
+{
+ return pvc->main != NULL || pvc->ether != NULL;
+}
+
+
+static inline void pvc_carrier(int on, pvc_device *pvc)
+{
+ if (on) {
+ if (pvc->main)
+ if (!netif_carrier_ok(pvc->main))
+ netif_carrier_on(pvc->main);
+ if (pvc->ether)
+ if (!netif_carrier_ok(pvc->ether))
+ netif_carrier_on(pvc->ether);
+ } else {
+ if (pvc->main)
+ if (netif_carrier_ok(pvc->main))
+ netif_carrier_off(pvc->main);
+ if (pvc->ether)
+ if (netif_carrier_ok(pvc->ether))
+ netif_carrier_off(pvc->ether);
+ }
+}
+
+
+static inline void delete_unused_pvcs(hdlc_device *hdlc)
+{
+ pvc_device **pvc_p = &hdlc->state.fr.first_pvc;
+
+ while (*pvc_p) {
+ if (!pvc_is_used(*pvc_p)) {
+ pvc_device *pvc = *pvc_p;
+ *pvc_p = pvc->next;
+ kfree(pvc);
+ continue;
+ }
+ pvc_p = &(*pvc_p)->next;
+ }
+}
+
+
+static inline struct net_device** get_dev_p(pvc_device *pvc, int type)
+{
+ if (type == ARPHRD_ETHER)
+ return &pvc->ether;
+ else
+ return &pvc->main;
+}
+
+
+static inline u16 status_to_dlci(u8 *status, int *active, int *new)
+{
+ *new = (status[2] & 0x08) ? 1 : 0;
+ *active = (status[2] & 0x02) ? 1 : 0;
+
+ return ((status[0] & 0x3F) << 4) | ((status[1] & 0x78) >> 3);
+}
+
+
+static inline void dlci_to_status(u16 dlci, u8 *status, int active, int new)
+{
+ status[0] = (dlci >> 4) & 0x3F;
+ status[1] = ((dlci << 3) & 0x78) | 0x80;
+ status[2] = 0x80;
+
+ if (new)
+ status[2] |= 0x08;
+ else if (active)
+ status[2] |= 0x02;
+}
+
+
+
+static int fr_hard_header(struct sk_buff **skb_p, u16 dlci)
+{
+ u16 head_len;
+ struct sk_buff *skb = *skb_p;
+
+ switch (skb->protocol) {
+ case __constant_ntohs(ETH_P_IP):
+ head_len = 4;
+ skb_push(skb, head_len);
+ skb->data[3] = NLPID_IP;
+ break;
+
+ case __constant_ntohs(ETH_P_IPV6):
+ head_len = 4;
+ skb_push(skb, head_len);
+ skb->data[3] = NLPID_IPV6;
+ break;
+
+ case __constant_ntohs(LMI_PROTO):
+ head_len = 4;
+ skb_push(skb, head_len);
+ skb->data[3] = LMI_PROTO;
+ break;
+
+ case __constant_ntohs(ETH_P_802_3):
+ head_len = 10;
+ if (skb_headroom(skb) < head_len) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb,
+ head_len);
+ if (!skb2)
+ return -ENOBUFS;
+ dev_kfree_skb(skb);
+ skb = *skb_p = skb2;
+ }
+ skb_push(skb, head_len);
+ skb->data[3] = FR_PAD;
+ skb->data[4] = NLPID_SNAP;
+ skb->data[5] = FR_PAD;
+ skb->data[6] = 0x80;
+ skb->data[7] = 0xC2;
+ skb->data[8] = 0x00;
+ skb->data[9] = 0x07; /* bridged Ethernet frame w/out FCS */
+ break;
+
+ default:
+ head_len = 10;
+ skb_push(skb, head_len);
+ skb->data[3] = FR_PAD;
+ skb->data[4] = NLPID_SNAP;
+ skb->data[5] = FR_PAD;
+ skb->data[6] = FR_PAD;
+ skb->data[7] = FR_PAD;
+ *(u16*)(skb->data + 8) = skb->protocol;
+ }
+
+ dlci_to_q922(skb->data, dlci);
+ skb->data[2] = FR_UI;
+ return 0;
+}
+
+
+
+static int pvc_open(struct net_device *dev)
+{
+ pvc_device *pvc = dev_to_pvc(dev);
+
+ if ((pvc->master->flags & IFF_UP) == 0)
+ return -EIO; /* Master must be UP in order to activate PVC */
+
+ if (pvc->open_count++ == 0) {
+ hdlc_device *hdlc = dev_to_hdlc(pvc->master);
+ if (hdlc->state.fr.settings.lmi == LMI_NONE)
+ pvc->state.active = hdlc->carrier;
+
+ pvc_carrier(pvc->state.active, pvc);
+ hdlc->state.fr.dce_changed = 1;
+ }
+ return 0;
+}
+
+
+
+static int pvc_close(struct net_device *dev)
+{
+ pvc_device *pvc = dev_to_pvc(dev);
+
+ if (--pvc->open_count == 0) {
+ hdlc_device *hdlc = dev_to_hdlc(pvc->master);
+ if (hdlc->state.fr.settings.lmi == LMI_NONE)
+ pvc->state.active = 0;
+
+ if (hdlc->state.fr.settings.dce) {
+ hdlc->state.fr.dce_changed = 1;
+ pvc->state.active = 0;
+ }
+ }
+ return 0;
+}
+
+
+
+int pvc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ pvc_device *pvc = dev_to_pvc(dev);
+ fr_proto_pvc_info info;
+
+ if (ifr->ifr_settings.type == IF_GET_PROTO) {
+ if (dev->type == ARPHRD_ETHER)
+ ifr->ifr_settings.type = IF_PROTO_FR_ETH_PVC;
+ else
+ ifr->ifr_settings.type = IF_PROTO_FR_PVC;
+
+ if (ifr->ifr_settings.size < sizeof(info)) {
+ /* data size wanted */
+ ifr->ifr_settings.size = sizeof(info);
+ return -ENOBUFS;
+ }
+
+ info.dlci = pvc->dlci;
+ memcpy(info.master, pvc->master->name, IFNAMSIZ);
+ if (copy_to_user(ifr->ifr_settings.ifs_ifsu.fr_pvc_info,
+ &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+
+static inline struct net_device_stats *pvc_get_stats(struct net_device *dev)
+{
+ return netdev_priv(dev);
+}
+
+
+
+static int pvc_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ pvc_device *pvc = dev_to_pvc(dev);
+ struct net_device_stats *stats = pvc_get_stats(dev);
+
+ if (pvc->state.active) {
+ if (dev->type == ARPHRD_ETHER) {
+ int pad = ETH_ZLEN - skb->len;
+ if (pad > 0) { /* Pad the frame with zeros */
+ int len = skb->len;
+ if (skb_tailroom(skb) < pad)
+ if (pskb_expand_head(skb, 0, pad,
+ GFP_ATOMIC)) {
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ skb_put(skb, pad);
+ memset(skb->data + len, 0, pad);
+ }
+ skb->protocol = __constant_htons(ETH_P_802_3);
+ }
+ if (!fr_hard_header(&skb, pvc->dlci)) {
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+ if (pvc->state.fecn) /* TX Congestion counter */
+ stats->tx_compressed++;
+ skb->dev = pvc->master;
+ dev_queue_xmit(skb);
+ return 0;
+ }
+ }
+
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+
+
+static int pvc_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if ((new_mtu < 68) || (new_mtu > HDLC_MAX_MTU))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+
+
+static inline void fr_log_dlci_active(pvc_device *pvc)
+{
+ printk(KERN_INFO "%s: DLCI %d [%s%s%s]%s %s\n",
+ pvc->master->name,
+ pvc->dlci,
+ pvc->main ? pvc->main->name : "",
+ pvc->main && pvc->ether ? " " : "",
+ pvc->ether ? pvc->ether->name : "",
+ pvc->state.new ? " new" : "",
+ !pvc->state.exist ? "deleted" :
+ pvc->state.active ? "active" : "inactive");
+}
+
+
+
+static inline u8 fr_lmi_nextseq(u8 x)
+{
+ x++;
+ return x ? x : 1;
+}
+
+
+
+static void fr_lmi_send(struct net_device *dev, int fullrep)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ struct sk_buff *skb;
+ pvc_device *pvc = hdlc->state.fr.first_pvc;
+ int len = (hdlc->state.fr.settings.lmi == LMI_ANSI) ? LMI_ANSI_LENGTH
+ : LMI_LENGTH;
+ int stat_len = 3;
+ u8 *data;
+ int i = 0;
+
+ if (hdlc->state.fr.settings.dce && fullrep) {
+ len += hdlc->state.fr.dce_pvc_count * (2 + stat_len);
+ if (len > HDLC_MAX_MRU) {
+ printk(KERN_WARNING "%s: Too many PVCs while sending "
+ "LMI full report\n", dev->name);
+ return;
+ }
+ }
+
+ skb = dev_alloc_skb(len);
+ if (!skb) {
+ printk(KERN_WARNING "%s: Memory squeeze on fr_lmi_send()\n",
+ dev->name);
+ return;
+ }
+ memset(skb->data, 0, len);
+ skb_reserve(skb, 4);
+ skb->protocol = __constant_htons(LMI_PROTO);
+ fr_hard_header(&skb, LMI_DLCI);
+ data = skb->tail;
+ data[i++] = LMI_CALLREF;
+ data[i++] = hdlc->state.fr.settings.dce
+ ? LMI_STATUS : LMI_STATUS_ENQUIRY;
+ if (hdlc->state.fr.settings.lmi == LMI_ANSI)
+ data[i++] = LMI_ANSI_LOCKSHIFT;
+ data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT)
+ ? LMI_CCITT_REPTYPE : LMI_REPTYPE;
+ data[i++] = LMI_REPT_LEN;
+ data[i++] = fullrep ? LMI_FULLREP : LMI_INTEGRITY;
+
+ data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT)
+ ? LMI_CCITT_ALIVE : LMI_ALIVE;
+ data[i++] = LMI_INTEG_LEN;
+ data[i++] = hdlc->state.fr.txseq =fr_lmi_nextseq(hdlc->state.fr.txseq);
+ data[i++] = hdlc->state.fr.rxseq;
+
+ if (hdlc->state.fr.settings.dce && fullrep) {
+ while (pvc) {
+ data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT)
+ ? LMI_CCITT_PVCSTAT : LMI_PVCSTAT;
+ data[i++] = stat_len;
+
+ /* LMI start/restart */
+ if (hdlc->state.fr.reliable && !pvc->state.exist) {
+ pvc->state.exist = pvc->state.new = 1;
+ fr_log_dlci_active(pvc);
+ }
+
+ /* ifconfig PVC up */
+ if (pvc->open_count && !pvc->state.active &&
+ pvc->state.exist && !pvc->state.new) {
+ pvc_carrier(1, pvc);
+ pvc->state.active = 1;
+ fr_log_dlci_active(pvc);
+ }
+
+ dlci_to_status(pvc->dlci, data + i,
+ pvc->state.active, pvc->state.new);
+ i += stat_len;
+ pvc = pvc->next;
+ }
+ }
+
+ skb_put(skb, i);
+ skb->priority = TC_PRIO_CONTROL;
+ skb->dev = dev;
+ skb->nh.raw = skb->data;
+
+ dev_queue_xmit(skb);
+}
+
+
+
+static void fr_set_link_state(int reliable, struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+ hdlc->state.fr.reliable = reliable;
+ if (reliable) {
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+
+ hdlc->state.fr.n391cnt = 0; /* Request full status */
+ hdlc->state.fr.dce_changed = 1;
+
+ if (hdlc->state.fr.settings.lmi == LMI_NONE) {
+ while (pvc) { /* Activate all PVCs */
+ pvc_carrier(1, pvc);
+ pvc->state.exist = pvc->state.active = 1;
+ pvc->state.new = 0;
+ pvc = pvc->next;
+ }
+ }
+ } else {
+ if (netif_carrier_ok(dev))
+ netif_carrier_off(dev);
+
+ while (pvc) { /* Deactivate all PVCs */
+ pvc_carrier(0, pvc);
+ pvc->state.exist = pvc->state.active = 0;
+ pvc->state.new = 0;
+ pvc = pvc->next;
+ }
+ }
+}
+
+
+
+static void fr_timer(unsigned long arg)
+{
+ struct net_device *dev = (struct net_device *)arg;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int i, cnt = 0, reliable;
+ u32 list;
+
+ if (hdlc->state.fr.settings.dce)
+ reliable = hdlc->state.fr.request &&
+ time_before(jiffies, hdlc->state.fr.last_poll +
+ hdlc->state.fr.settings.t392 * HZ);
+ else {
+ hdlc->state.fr.last_errors <<= 1; /* Shift the list */
+ if (hdlc->state.fr.request) {
+ if (hdlc->state.fr.reliable)
+ printk(KERN_INFO "%s: No LMI status reply "
+ "received\n", dev->name);
+ hdlc->state.fr.last_errors |= 1;
+ }
+
+ list = hdlc->state.fr.last_errors;
+ for (i = 0; i < hdlc->state.fr.settings.n393; i++, list >>= 1)
+ cnt += (list & 1); /* errors count */
+
+ reliable = (cnt < hdlc->state.fr.settings.n392);
+ }
+
+ if (hdlc->state.fr.reliable != reliable) {
+ printk(KERN_INFO "%s: Link %sreliable\n", dev->name,
+ reliable ? "" : "un");
+ fr_set_link_state(reliable, dev);
+ }
+
+ if (hdlc->state.fr.settings.dce)
+ hdlc->state.fr.timer.expires = jiffies +
+ hdlc->state.fr.settings.t392 * HZ;
+ else {
+ if (hdlc->state.fr.n391cnt)
+ hdlc->state.fr.n391cnt--;
+
+ fr_lmi_send(dev, hdlc->state.fr.n391cnt == 0);
+
+ hdlc->state.fr.last_poll = jiffies;
+ hdlc->state.fr.request = 1;
+ hdlc->state.fr.timer.expires = jiffies +
+ hdlc->state.fr.settings.t391 * HZ;
+ }
+
+ hdlc->state.fr.timer.function = fr_timer;
+ hdlc->state.fr.timer.data = arg;
+ add_timer(&hdlc->state.fr.timer);
+}
+
+
+
+static int fr_lmi_recv(struct net_device *dev, struct sk_buff *skb)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int stat_len;
+ pvc_device *pvc;
+ int reptype = -1, error, no_ram;
+ u8 rxseq, txseq;
+ int i;
+
+ if (skb->len < ((hdlc->state.fr.settings.lmi == LMI_ANSI)
+ ? LMI_ANSI_LENGTH : LMI_LENGTH)) {
+ printk(KERN_INFO "%s: Short LMI frame\n", dev->name);
+ return 1;
+ }
+
+ if (skb->data[5] != (!hdlc->state.fr.settings.dce ?
+ LMI_STATUS : LMI_STATUS_ENQUIRY)) {
+ printk(KERN_INFO "%s: LMI msgtype=%x, Not LMI status %s\n",
+ dev->name, skb->data[2],
+ hdlc->state.fr.settings.dce ? "enquiry" : "reply");
+ return 1;
+ }
+
+ i = (hdlc->state.fr.settings.lmi == LMI_ANSI) ? 7 : 6;
+
+ if (skb->data[i] !=
+ ((hdlc->state.fr.settings.lmi == LMI_CCITT)
+ ? LMI_CCITT_REPTYPE : LMI_REPTYPE)) {
+ printk(KERN_INFO "%s: Not a report type=%x\n",
+ dev->name, skb->data[i]);
+ return 1;
+ }
+ i++;
+
+ i++; /* Skip length field */
+
+ reptype = skb->data[i++];
+
+ if (skb->data[i]!=
+ ((hdlc->state.fr.settings.lmi == LMI_CCITT)
+ ? LMI_CCITT_ALIVE : LMI_ALIVE)) {
+ printk(KERN_INFO "%s: Unsupported status element=%x\n",
+ dev->name, skb->data[i]);
+ return 1;
+ }
+ i++;
+
+ i++; /* Skip length field */
+
+ hdlc->state.fr.rxseq = skb->data[i++]; /* TX sequence from peer */
+ rxseq = skb->data[i++]; /* Should confirm our sequence */
+
+ txseq = hdlc->state.fr.txseq;
+
+ if (hdlc->state.fr.settings.dce) {
+ if (reptype != LMI_FULLREP && reptype != LMI_INTEGRITY) {
+ printk(KERN_INFO "%s: Unsupported report type=%x\n",
+ dev->name, reptype);
+ return 1;
+ }
+ hdlc->state.fr.last_poll = jiffies;
+ }
+
+ error = 0;
+ if (!hdlc->state.fr.reliable)
+ error = 1;
+
+ if (rxseq == 0 || rxseq != txseq) {
+ hdlc->state.fr.n391cnt = 0; /* Ask for full report next time */
+ error = 1;
+ }
+
+ if (hdlc->state.fr.settings.dce) {
+ if (hdlc->state.fr.fullrep_sent && !error) {
+/* Stop sending full report - the last one has been confirmed by DTE */
+ hdlc->state.fr.fullrep_sent = 0;
+ pvc = hdlc->state.fr.first_pvc;
+ while (pvc) {
+ if (pvc->state.new) {
+ pvc->state.new = 0;
+
+/* Tell DTE that new PVC is now active */
+ hdlc->state.fr.dce_changed = 1;
+ }
+ pvc = pvc->next;
+ }
+ }
+
+ if (hdlc->state.fr.dce_changed) {
+ reptype = LMI_FULLREP;
+ hdlc->state.fr.fullrep_sent = 1;
+ hdlc->state.fr.dce_changed = 0;
+ }
+
+ fr_lmi_send(dev, reptype == LMI_FULLREP ? 1 : 0);
+ return 0;
+ }
+
+ /* DTE */
+
+ hdlc->state.fr.request = 0; /* got response, no request pending */
+
+ if (error)
+ return 0;
+
+ if (reptype != LMI_FULLREP)
+ return 0;
+
+ stat_len = 3;
+ pvc = hdlc->state.fr.first_pvc;
+
+ while (pvc) {
+ pvc->state.deleted = 1;
+ pvc = pvc->next;
+ }
+
+ no_ram = 0;
+ while (skb->len >= i + 2 + stat_len) {
+ u16 dlci;
+ unsigned int active, new;
+
+ if (skb->data[i] != ((hdlc->state.fr.settings.lmi == LMI_CCITT)
+ ? LMI_CCITT_PVCSTAT : LMI_PVCSTAT)) {
+ printk(KERN_WARNING "%s: Invalid PVCSTAT ID: %x\n",
+ dev->name, skb->data[i]);
+ return 1;
+ }
+ i++;
+
+ if (skb->data[i] != stat_len) {
+ printk(KERN_WARNING "%s: Invalid PVCSTAT length: %x\n",
+ dev->name, skb->data[i]);
+ return 1;
+ }
+ i++;
+
+ dlci = status_to_dlci(skb->data + i, &active, &new);
+
+ pvc = add_pvc(dev, dlci);
+
+ if (!pvc && !no_ram) {
+ printk(KERN_WARNING
+ "%s: Memory squeeze on fr_lmi_recv()\n",
+ dev->name);
+ no_ram = 1;
+ }
+
+ if (pvc) {
+ pvc->state.exist = 1;
+ pvc->state.deleted = 0;
+ if (active != pvc->state.active ||
+ new != pvc->state.new ||
+ !pvc->state.exist) {
+ pvc->state.new = new;
+ pvc->state.active = active;
+ pvc_carrier(active, pvc);
+ fr_log_dlci_active(pvc);
+ }
+ }
+
+ i += stat_len;
+ }
+
+ pvc = hdlc->state.fr.first_pvc;
+
+ while (pvc) {
+ if (pvc->state.deleted && pvc->state.exist) {
+ pvc_carrier(0, pvc);
+ pvc->state.active = pvc->state.new = 0;
+ pvc->state.exist = 0;
+ fr_log_dlci_active(pvc);
+ }
+ pvc = pvc->next;
+ }
+
+ /* Next full report after N391 polls */
+ hdlc->state.fr.n391cnt = hdlc->state.fr.settings.n391;
+
+ return 0;
+}
+
+
+
+static int fr_rx(struct sk_buff *skb)
+{
+ struct net_device *ndev = skb->dev;
+ hdlc_device *hdlc = dev_to_hdlc(ndev);
+ fr_hdr *fh = (fr_hdr*)skb->data;
+ u8 *data = skb->data;
+ u16 dlci;
+ pvc_device *pvc;
+ struct net_device *dev = NULL;
+
+ if (skb->len <= 4 || fh->ea1 || data[2] != FR_UI)
+ goto rx_error;
+
+ dlci = q922_to_dlci(skb->data);
+
+ if (dlci == LMI_DLCI) {
+ if (hdlc->state.fr.settings.lmi == LMI_NONE)
+ goto rx_error; /* LMI packet with no LMI? */
+
+ if (data[3] == LMI_PROTO) {
+ if (fr_lmi_recv(ndev, skb))
+ goto rx_error;
+ else {
+ dev_kfree_skb_any(skb);
+ return NET_RX_SUCCESS;
+ }
+ }
+
+ printk(KERN_INFO "%s: Received non-LMI frame with LMI DLCI\n",
+ ndev->name);
+ goto rx_error;
+ }
+
+ pvc = find_pvc(hdlc, dlci);
+ if (!pvc) {
+#ifdef DEBUG_PKT
+ printk(KERN_INFO "%s: No PVC for received frame's DLCI %d\n",
+ ndev->name, dlci);
+#endif
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+ }
+
+ if (pvc->state.fecn != fh->fecn) {
+#ifdef DEBUG_ECN
+ printk(KERN_DEBUG "%s: DLCI %d FECN O%s\n", ndev->name,
+ dlci, fh->fecn ? "N" : "FF");
+#endif
+ pvc->state.fecn ^= 1;
+ }
+
+ if (pvc->state.becn != fh->becn) {
+#ifdef DEBUG_ECN
+ printk(KERN_DEBUG "%s: DLCI %d BECN O%s\n", ndev->name,
+ dlci, fh->becn ? "N" : "FF");
+#endif
+ pvc->state.becn ^= 1;
+ }
+
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
+ hdlc->stats.rx_dropped++;
+ return NET_RX_DROP;
+ }
+
+ if (data[3] == NLPID_IP) {
+ skb_pull(skb, 4); /* Remove 4-byte header (hdr, UI, NLPID) */
+ dev = pvc->main;
+ skb->protocol = htons(ETH_P_IP);
+
+ } else if (data[3] == NLPID_IPV6) {
+ skb_pull(skb, 4); /* Remove 4-byte header (hdr, UI, NLPID) */
+ dev = pvc->main;
+ skb->protocol = htons(ETH_P_IPV6);
+
+ } else if (skb->len > 10 && data[3] == FR_PAD &&
+ data[4] == NLPID_SNAP && data[5] == FR_PAD) {
+ u16 oui = ntohs(*(u16*)(data + 6));
+ u16 pid = ntohs(*(u16*)(data + 8));
+ skb_pull(skb, 10);
+
+ switch ((((u32)oui) << 16) | pid) {
+ case ETH_P_ARP: /* routed frame with SNAP */
+ case ETH_P_IPX:
+ case ETH_P_IP: /* a long variant */
+ case ETH_P_IPV6:
+ dev = pvc->main;
+ skb->protocol = htons(pid);
+ break;
+
+ case 0x80C20007: /* bridged Ethernet frame */
+ if ((dev = pvc->ether) != NULL)
+ skb->protocol = eth_type_trans(skb, dev);
+ break;
+
+ default:
+ printk(KERN_INFO "%s: Unsupported protocol, OUI=%x "
+ "PID=%x\n", ndev->name, oui, pid);
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+ }
+ } else {
+ printk(KERN_INFO "%s: Unsupported protocol, NLPID=%x "
+ "length = %i\n", ndev->name, data[3], skb->len);
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+ }
+
+ if (dev) {
+ struct net_device_stats *stats = pvc_get_stats(dev);
+ stats->rx_packets++; /* PVC traffic */
+ stats->rx_bytes += skb->len;
+ if (pvc->state.becn)
+ stats->rx_compressed++;
+ skb->dev = dev;
+ netif_rx(skb);
+ return NET_RX_SUCCESS;
+ } else {
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+ }
+
+ rx_error:
+ hdlc->stats.rx_errors++; /* Mark error */
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+}
+
+
+
+static void fr_start(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "fr_start\n");
+#endif
+ if (hdlc->state.fr.settings.lmi != LMI_NONE) {
+ hdlc->state.fr.reliable = 0;
+ hdlc->state.fr.dce_changed = 1;
+ hdlc->state.fr.request = 0;
+ hdlc->state.fr.fullrep_sent = 0;
+ hdlc->state.fr.last_errors = 0xFFFFFFFF;
+ hdlc->state.fr.n391cnt = 0;
+ hdlc->state.fr.txseq = hdlc->state.fr.rxseq = 0;
+
+ init_timer(&hdlc->state.fr.timer);
+ /* First poll after 1 s */
+ hdlc->state.fr.timer.expires = jiffies + HZ;
+ hdlc->state.fr.timer.function = fr_timer;
+ hdlc->state.fr.timer.data = (unsigned long)dev;
+ add_timer(&hdlc->state.fr.timer);
+ } else
+ fr_set_link_state(1, dev);
+}
+
+
+
+static void fr_stop(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "fr_stop\n");
+#endif
+ if (hdlc->state.fr.settings.lmi != LMI_NONE)
+ del_timer_sync(&hdlc->state.fr.timer);
+ fr_set_link_state(0, dev);
+}
+
+
+
+static void fr_close(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+ while (pvc) { /* Shutdown all PVCs for this FRAD */
+ if (pvc->main)
+ dev_close(pvc->main);
+ if (pvc->ether)
+ dev_close(pvc->ether);
+ pvc = pvc->next;
+ }
+}
+
+static void dlci_setup(struct net_device *dev)
+{
+ dev->type = ARPHRD_DLCI;
+ dev->flags = IFF_POINTOPOINT;
+ dev->hard_header_len = 10;
+ dev->addr_len = 2;
+}
+
+static int fr_add_pvc(struct net_device *master, unsigned int dlci, int type)
+{
+ hdlc_device *hdlc = dev_to_hdlc(master);
+ pvc_device *pvc = NULL;
+ struct net_device *dev;
+ int result, used;
+ char * prefix = "pvc%d";
+
+ if (type == ARPHRD_ETHER)
+ prefix = "pvceth%d";
+
+ if ((pvc = add_pvc(master, dlci)) == NULL) {
+ printk(KERN_WARNING "%s: Memory squeeze on fr_add_pvc()\n",
+ master->name);
+ return -ENOBUFS;
+ }
+
+ if (*get_dev_p(pvc, type))
+ return -EEXIST;
+
+ used = pvc_is_used(pvc);
+
+ if (type == ARPHRD_ETHER)
+ dev = alloc_netdev(sizeof(struct net_device_stats),
+ "pvceth%d", ether_setup);
+ else
+ dev = alloc_netdev(sizeof(struct net_device_stats),
+ "pvc%d", dlci_setup);
+
+ if (!dev) {
+ printk(KERN_WARNING "%s: Memory squeeze on fr_pvc()\n",
+ master->name);
+ delete_unused_pvcs(hdlc);
+ return -ENOBUFS;
+ }
+
+ if (type == ARPHRD_ETHER) {
+ memcpy(dev->dev_addr, "\x00\x01", 2);
+ get_random_bytes(dev->dev_addr + 2, ETH_ALEN - 2);
+ } else {
+ *(u16*)dev->dev_addr = htons(dlci);
+ dlci_to_q922(dev->broadcast, dlci);
+ }
+ dev->hard_start_xmit = pvc_xmit;
+ dev->get_stats = pvc_get_stats;
+ dev->open = pvc_open;
+ dev->stop = pvc_close;
+ dev->do_ioctl = pvc_ioctl;
+ dev->change_mtu = pvc_change_mtu;
+ dev->mtu = HDLC_MAX_MTU;
+ dev->tx_queue_len = 0;
+ dev->priv = pvc;
+
+ result = dev_alloc_name(dev, dev->name);
+ if (result < 0) {
+ free_netdev(dev);
+ delete_unused_pvcs(hdlc);
+ return result;
+ }
+
+ if (register_netdevice(dev) != 0) {
+ free_netdev(dev);
+ delete_unused_pvcs(hdlc);
+ return -EIO;
+ }
+
+ dev->destructor = free_netdev;
+ *get_dev_p(pvc, type) = dev;
+ if (!used) {
+ hdlc->state.fr.dce_changed = 1;
+ hdlc->state.fr.dce_pvc_count++;
+ }
+ return 0;
+}
+
+
+
+static int fr_del_pvc(hdlc_device *hdlc, unsigned int dlci, int type)
+{
+ pvc_device *pvc;
+ struct net_device *dev;
+
+ if ((pvc = find_pvc(hdlc, dlci)) == NULL)
+ return -ENOENT;
+
+ if ((dev = *get_dev_p(pvc, type)) == NULL)
+ return -ENOENT;
+
+ if (dev->flags & IFF_UP)
+ return -EBUSY; /* PVC in use */
+
+ unregister_netdevice(dev); /* the destructor will free_netdev(dev) */
+ *get_dev_p(pvc, type) = NULL;
+
+ if (!pvc_is_used(pvc)) {
+ hdlc->state.fr.dce_pvc_count--;
+ hdlc->state.fr.dce_changed = 1;
+ }
+ delete_unused_pvcs(hdlc);
+ return 0;
+}
+
+
+
+static void fr_destroy(hdlc_device *hdlc)
+{
+ pvc_device *pvc;
+
+ pvc = hdlc->state.fr.first_pvc;
+ hdlc->state.fr.first_pvc = NULL; /* All PVCs destroyed */
+ hdlc->state.fr.dce_pvc_count = 0;
+ hdlc->state.fr.dce_changed = 1;
+
+ while (pvc) {
+ pvc_device *next = pvc->next;
+ /* destructors will free_netdev() main and ether */
+ if (pvc->main)
+ unregister_netdevice(pvc->main);
+
+ if (pvc->ether)
+ unregister_netdevice(pvc->ether);
+
+ kfree(pvc);
+ pvc = next;
+ }
+}
+
+
+
+int hdlc_fr_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+ fr_proto __user *fr_s = ifr->ifr_settings.ifs_ifsu.fr;
+ const size_t size = sizeof(fr_proto);
+ fr_proto new_settings;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ fr_proto_pvc pvc;
+ int result;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_PROTO:
+ ifr->ifr_settings.type = IF_PROTO_FR;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(fr_s, &hdlc->state.fr.settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_PROTO_FR:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if(dev->flags & IFF_UP)
+ return -EBUSY;
+
+ if (copy_from_user(&new_settings, fr_s, size))
+ return -EFAULT;
+
+ if (new_settings.lmi == LMI_DEFAULT)
+ new_settings.lmi = LMI_ANSI;
+
+ if ((new_settings.lmi != LMI_NONE &&
+ new_settings.lmi != LMI_ANSI &&
+ new_settings.lmi != LMI_CCITT) ||
+ new_settings.t391 < 1 ||
+ new_settings.t392 < 2 ||
+ new_settings.n391 < 1 ||
+ new_settings.n392 < 1 ||
+ new_settings.n393 < new_settings.n392 ||
+ new_settings.n393 > 32 ||
+ (new_settings.dce != 0 &&
+ new_settings.dce != 1))
+ return -EINVAL;
+
+ result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+ if (result)
+ return result;
+
+ if (hdlc->proto.id != IF_PROTO_FR) {
+ hdlc_proto_detach(hdlc);
+ hdlc->state.fr.first_pvc = NULL;
+ hdlc->state.fr.dce_pvc_count = 0;
+ }
+ memcpy(&hdlc->state.fr.settings, &new_settings, size);
+ memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+ hdlc->proto.close = fr_close;
+ hdlc->proto.start = fr_start;
+ hdlc->proto.stop = fr_stop;
+ hdlc->proto.detach = fr_destroy;
+ hdlc->proto.netif_rx = fr_rx;
+ hdlc->proto.id = IF_PROTO_FR;
+ dev->hard_start_xmit = hdlc->xmit;
+ dev->hard_header = NULL;
+ dev->type = ARPHRD_FRAD;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->addr_len = 0;
+ return 0;
+
+ case IF_PROTO_FR_ADD_PVC:
+ case IF_PROTO_FR_DEL_PVC:
+ case IF_PROTO_FR_ADD_ETH_PVC:
+ case IF_PROTO_FR_DEL_ETH_PVC:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&pvc, ifr->ifr_settings.ifs_ifsu.fr_pvc,
+ sizeof(fr_proto_pvc)))
+ return -EFAULT;
+
+ if (pvc.dlci <= 0 || pvc.dlci >= 1024)
+ return -EINVAL; /* Only 10 bits, DLCI 0 reserved */
+
+ if (ifr->ifr_settings.type == IF_PROTO_FR_ADD_ETH_PVC ||
+ ifr->ifr_settings.type == IF_PROTO_FR_DEL_ETH_PVC)
+ result = ARPHRD_ETHER; /* bridged Ethernet device */
+ else
+ result = ARPHRD_DLCI;
+
+ if (ifr->ifr_settings.type == IF_PROTO_FR_ADD_PVC ||
+ ifr->ifr_settings.type == IF_PROTO_FR_ADD_ETH_PVC)
+ return fr_add_pvc(dev, pvc.dlci, result);
+ else
+ return fr_del_pvc(hdlc, pvc.dlci, result);
+ }
+
+ return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_generic.c b/drivers/net/wan/hdlc_generic.c
new file mode 100644
index 00000000000..6ed064cb446
--- /dev/null
+++ b/drivers/net/wan/hdlc_generic.c
@@ -0,0 +1,343 @@
+/*
+ * Generic HDLC support routines for Linux
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * Currently supported:
+ * * raw IP-in-HDLC
+ * * Cisco HDLC
+ * * Frame Relay with ANSI or CCITT LMI (both user and network side)
+ * * PPP
+ * * X.25
+ *
+ * Use sethdlc utility to set line parameters, protocol and PVCs
+ *
+ * How does it work:
+ * - proto.open(), close(), start(), stop() calls are serialized.
+ * The order is: open, [ start, stop ... ] close ...
+ * - proto.start() and stop() are called with spin_lock_irq held.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+
+static const char* version = "HDLC support module revision 1.17";
+
+#undef DEBUG_LINK
+
+
+static int hdlc_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if ((new_mtu < 68) || (new_mtu > HDLC_MAX_MTU))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+
+
+static struct net_device_stats *hdlc_get_stats(struct net_device *dev)
+{
+ return hdlc_stats(dev);
+}
+
+
+
+static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *p)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ if (hdlc->proto.netif_rx)
+ return hdlc->proto.netif_rx(skb);
+
+ hdlc->stats.rx_dropped++; /* Shouldn't happen */
+ dev_kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+
+
+static void __hdlc_set_carrier_on(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ if (hdlc->proto.start)
+ return hdlc->proto.start(dev);
+#ifdef DEBUG_LINK
+ if (netif_carrier_ok(dev))
+ printk(KERN_ERR "hdlc_set_carrier_on(): already on\n");
+#endif
+ netif_carrier_on(dev);
+}
+
+
+
+static void __hdlc_set_carrier_off(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ if (hdlc->proto.stop)
+ return hdlc->proto.stop(dev);
+
+#ifdef DEBUG_LINK
+ if (!netif_carrier_ok(dev))
+ printk(KERN_ERR "hdlc_set_carrier_off(): already off\n");
+#endif
+ netif_carrier_off(dev);
+}
+
+
+
+void hdlc_set_carrier(int on, struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ unsigned long flags;
+ on = on ? 1 : 0;
+
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "hdlc_set_carrier %i\n", on);
+#endif
+
+ spin_lock_irqsave(&hdlc->state_lock, flags);
+
+ if (hdlc->carrier == on)
+ goto carrier_exit; /* no change in DCD line level */
+
+#ifdef DEBUG_LINK
+ printk(KERN_INFO "%s: carrier %s\n", dev->name, on ? "ON" : "off");
+#endif
+ hdlc->carrier = on;
+
+ if (!hdlc->open)
+ goto carrier_exit;
+
+ if (hdlc->carrier)
+ __hdlc_set_carrier_on(dev);
+ else
+ __hdlc_set_carrier_off(dev);
+
+carrier_exit:
+ spin_unlock_irqrestore(&hdlc->state_lock, flags);
+}
+
+
+
+/* Must be called by hardware driver when HDLC device is being opened */
+int hdlc_open(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "hdlc_open() carrier %i open %i\n",
+ hdlc->carrier, hdlc->open);
+#endif
+
+ if (hdlc->proto.id == -1)
+ return -ENOSYS; /* no protocol attached */
+
+ if (hdlc->proto.open) {
+ int result = hdlc->proto.open(dev);
+ if (result)
+ return result;
+ }
+
+ spin_lock_irq(&hdlc->state_lock);
+
+ if (hdlc->carrier)
+ __hdlc_set_carrier_on(dev);
+
+ hdlc->open = 1;
+
+ spin_unlock_irq(&hdlc->state_lock);
+ return 0;
+}
+
+
+
+/* Must be called by hardware driver when HDLC device is being closed */
+void hdlc_close(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+#ifdef DEBUG_LINK
+ printk(KERN_DEBUG "hdlc_close() carrier %i open %i\n",
+ hdlc->carrier, hdlc->open);
+#endif
+
+ spin_lock_irq(&hdlc->state_lock);
+
+ hdlc->open = 0;
+ if (hdlc->carrier)
+ __hdlc_set_carrier_off(dev);
+
+ spin_unlock_irq(&hdlc->state_lock);
+
+ if (hdlc->proto.close)
+ hdlc->proto.close(dev);
+}
+
+
+
+#ifndef CONFIG_HDLC_RAW
+#define hdlc_raw_ioctl(dev, ifr) -ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_RAW_ETH
+#define hdlc_raw_eth_ioctl(dev, ifr) -ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_PPP
+#define hdlc_ppp_ioctl(dev, ifr) -ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_CISCO
+#define hdlc_cisco_ioctl(dev, ifr) -ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_FR
+#define hdlc_fr_ioctl(dev, ifr) -ENOSYS
+#endif
+
+#ifndef CONFIG_HDLC_X25
+#define hdlc_x25_ioctl(dev, ifr) -ENOSYS
+#endif
+
+
+int hdlc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ unsigned int proto;
+
+ if (cmd != SIOCWANDEV)
+ return -EINVAL;
+
+ switch(ifr->ifr_settings.type) {
+ case IF_PROTO_HDLC:
+ case IF_PROTO_HDLC_ETH:
+ case IF_PROTO_PPP:
+ case IF_PROTO_CISCO:
+ case IF_PROTO_FR:
+ case IF_PROTO_X25:
+ proto = ifr->ifr_settings.type;
+ break;
+
+ default:
+ proto = hdlc->proto.id;
+ }
+
+ switch(proto) {
+ case IF_PROTO_HDLC: return hdlc_raw_ioctl(dev, ifr);
+ case IF_PROTO_HDLC_ETH: return hdlc_raw_eth_ioctl(dev, ifr);
+ case IF_PROTO_PPP: return hdlc_ppp_ioctl(dev, ifr);
+ case IF_PROTO_CISCO: return hdlc_cisco_ioctl(dev, ifr);
+ case IF_PROTO_FR: return hdlc_fr_ioctl(dev, ifr);
+ case IF_PROTO_X25: return hdlc_x25_ioctl(dev, ifr);
+ default: return -EINVAL;
+ }
+}
+
+static void hdlc_setup(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ dev->get_stats = hdlc_get_stats;
+ dev->change_mtu = hdlc_change_mtu;
+ dev->mtu = HDLC_MAX_MTU;
+
+ dev->type = ARPHRD_RAWHDLC;
+ dev->hard_header_len = 16;
+
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+
+ hdlc->proto.id = -1;
+ hdlc->proto.detach = NULL;
+ hdlc->carrier = 1;
+ hdlc->open = 0;
+ spin_lock_init(&hdlc->state_lock);
+}
+
+struct net_device *alloc_hdlcdev(void *priv)
+{
+ struct net_device *dev;
+ dev = alloc_netdev(sizeof(hdlc_device), "hdlc%d", hdlc_setup);
+ if (dev)
+ dev_to_hdlc(dev)->priv = priv;
+ return dev;
+}
+
+int register_hdlc_device(struct net_device *dev)
+{
+ int result = dev_alloc_name(dev, "hdlc%d");
+ if (result < 0)
+ return result;
+
+ result = register_netdev(dev);
+ if (result != 0)
+ return -EIO;
+
+ if (netif_carrier_ok(dev))
+ netif_carrier_off(dev); /* no carrier until DCD goes up */
+
+ return 0;
+}
+
+
+
+void unregister_hdlc_device(struct net_device *dev)
+{
+ rtnl_lock();
+ hdlc_proto_detach(dev_to_hdlc(dev));
+ unregister_netdevice(dev);
+ rtnl_unlock();
+}
+
+
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("HDLC support module");
+MODULE_LICENSE("GPL v2");
+
+EXPORT_SYMBOL(hdlc_open);
+EXPORT_SYMBOL(hdlc_close);
+EXPORT_SYMBOL(hdlc_set_carrier);
+EXPORT_SYMBOL(hdlc_ioctl);
+EXPORT_SYMBOL(alloc_hdlcdev);
+EXPORT_SYMBOL(register_hdlc_device);
+EXPORT_SYMBOL(unregister_hdlc_device);
+
+static struct packet_type hdlc_packet_type = {
+ .type = __constant_htons(ETH_P_HDLC),
+ .func = hdlc_rcv,
+};
+
+
+static int __init hdlc_module_init(void)
+{
+ printk(KERN_INFO "%s\n", version);
+ dev_add_pack(&hdlc_packet_type);
+ return 0;
+}
+
+
+
+static void __exit hdlc_module_exit(void)
+{
+ dev_remove_pack(&hdlc_packet_type);
+}
+
+
+module_init(hdlc_module_init);
+module_exit(hdlc_module_exit);
diff --git a/drivers/net/wan/hdlc_ppp.c b/drivers/net/wan/hdlc_ppp.c
new file mode 100644
index 00000000000..7cd6195a2e4
--- /dev/null
+++ b/drivers/net/wan/hdlc_ppp.c
@@ -0,0 +1,115 @@
+/*
+ * Generic HDLC support routines for Linux
+ * Point-to-point protocol support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+
+static int ppp_open(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ void *old_ioctl;
+ int result;
+
+ dev->priv = &hdlc->state.ppp.syncppp_ptr;
+ hdlc->state.ppp.syncppp_ptr = &hdlc->state.ppp.pppdev;
+ hdlc->state.ppp.pppdev.dev = dev;
+
+ old_ioctl = dev->do_ioctl;
+ hdlc->state.ppp.old_change_mtu = dev->change_mtu;
+ sppp_attach(&hdlc->state.ppp.pppdev);
+ /* sppp_attach nukes them. We don't need syncppp's ioctl */
+ dev->do_ioctl = old_ioctl;
+ hdlc->state.ppp.pppdev.sppp.pp_flags &= ~PP_CISCO;
+ dev->type = ARPHRD_PPP;
+ result = sppp_open(dev);
+ if (result) {
+ sppp_detach(dev);
+ return result;
+ }
+
+ return 0;
+}
+
+
+
+static void ppp_close(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ sppp_close(dev);
+ sppp_detach(dev);
+ dev->rebuild_header = NULL;
+ dev->change_mtu = hdlc->state.ppp.old_change_mtu;
+ dev->mtu = HDLC_MAX_MTU;
+ dev->hard_header_len = 16;
+}
+
+
+
+static unsigned short ppp_type_trans(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ return __constant_htons(ETH_P_WAN_PPP);
+}
+
+
+
+int hdlc_ppp_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int result;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_PROTO:
+ ifr->ifr_settings.type = IF_PROTO_PPP;
+ return 0; /* return protocol only, no settable parameters */
+
+ case IF_PROTO_PPP:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if(dev->flags & IFF_UP)
+ return -EBUSY;
+
+ /* no settable parameters */
+
+ result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+ if (result)
+ return result;
+
+ hdlc_proto_detach(hdlc);
+ memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+ hdlc->proto.open = ppp_open;
+ hdlc->proto.close = ppp_close;
+ hdlc->proto.type_trans = ppp_type_trans;
+ hdlc->proto.id = IF_PROTO_PPP;
+ dev->hard_start_xmit = hdlc->xmit;
+ dev->hard_header = NULL;
+ dev->type = ARPHRD_PPP;
+ dev->addr_len = 0;
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_raw.c b/drivers/net/wan/hdlc_raw.c
new file mode 100644
index 00000000000..c41fb70b692
--- /dev/null
+++ b/drivers/net/wan/hdlc_raw.c
@@ -0,0 +1,90 @@
+/*
+ * Generic HDLC support routines for Linux
+ * HDLC support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+
+static unsigned short raw_type_trans(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ return __constant_htons(ETH_P_IP);
+}
+
+
+
+int hdlc_raw_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+ raw_hdlc_proto __user *raw_s = ifr->ifr_settings.ifs_ifsu.raw_hdlc;
+ const size_t size = sizeof(raw_hdlc_proto);
+ raw_hdlc_proto new_settings;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int result;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_PROTO:
+ ifr->ifr_settings.type = IF_PROTO_HDLC;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(raw_s, &hdlc->state.raw_hdlc.settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_PROTO_HDLC:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+
+ if (copy_from_user(&new_settings, raw_s, size))
+ return -EFAULT;
+
+ if (new_settings.encoding == ENCODING_DEFAULT)
+ new_settings.encoding = ENCODING_NRZ;
+
+ if (new_settings.parity == PARITY_DEFAULT)
+ new_settings.parity = PARITY_CRC16_PR1_CCITT;
+
+ result = hdlc->attach(dev, new_settings.encoding,
+ new_settings.parity);
+ if (result)
+ return result;
+
+ hdlc_proto_detach(hdlc);
+ memcpy(&hdlc->state.raw_hdlc.settings, &new_settings, size);
+ memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+ hdlc->proto.type_trans = raw_type_trans;
+ hdlc->proto.id = IF_PROTO_HDLC;
+ dev->hard_start_xmit = hdlc->xmit;
+ dev->hard_header = NULL;
+ dev->type = ARPHRD_RAWHDLC;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->addr_len = 0;
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_raw_eth.c b/drivers/net/wan/hdlc_raw_eth.c
new file mode 100644
index 00000000000..b1285cc8fee
--- /dev/null
+++ b/drivers/net/wan/hdlc_raw_eth.c
@@ -0,0 +1,107 @@
+/*
+ * Generic HDLC support routines for Linux
+ * HDLC Ethernet emulation support
+ *
+ * Copyright (C) 2002-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/random.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <linux/hdlc.h>
+
+
+static int eth_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ int pad = ETH_ZLEN - skb->len;
+ if (pad > 0) { /* Pad the frame with zeros */
+ int len = skb->len;
+ if (skb_tailroom(skb) < pad)
+ if (pskb_expand_head(skb, 0, pad, GFP_ATOMIC)) {
+ hdlc_stats(dev)->tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ skb_put(skb, pad);
+ memset(skb->data + len, 0, pad);
+ }
+ return dev_to_hdlc(dev)->xmit(skb, dev);
+}
+
+
+int hdlc_raw_eth_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+ raw_hdlc_proto __user *raw_s = ifr->ifr_settings.ifs_ifsu.raw_hdlc;
+ const size_t size = sizeof(raw_hdlc_proto);
+ raw_hdlc_proto new_settings;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int result;
+ void *old_ch_mtu;
+ int old_qlen;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_PROTO:
+ ifr->ifr_settings.type = IF_PROTO_HDLC_ETH;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(raw_s, &hdlc->state.raw_hdlc.settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_PROTO_HDLC_ETH:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+
+ if (copy_from_user(&new_settings, raw_s, size))
+ return -EFAULT;
+
+ if (new_settings.encoding == ENCODING_DEFAULT)
+ new_settings.encoding = ENCODING_NRZ;
+
+ if (new_settings.parity == PARITY_DEFAULT)
+ new_settings.parity = PARITY_CRC16_PR1_CCITT;
+
+ result = hdlc->attach(dev, new_settings.encoding,
+ new_settings.parity);
+ if (result)
+ return result;
+
+ hdlc_proto_detach(hdlc);
+ memcpy(&hdlc->state.raw_hdlc.settings, &new_settings, size);
+ memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+ hdlc->proto.type_trans = eth_type_trans;
+ hdlc->proto.id = IF_PROTO_HDLC_ETH;
+ dev->hard_start_xmit = eth_tx;
+ old_ch_mtu = dev->change_mtu;
+ old_qlen = dev->tx_queue_len;
+ ether_setup(dev);
+ dev->change_mtu = old_ch_mtu;
+ dev->tx_queue_len = old_qlen;
+ memcpy(dev->dev_addr, "\x00\x01", 2);
+ get_random_bytes(dev->dev_addr + 2, ETH_ALEN - 2);
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/drivers/net/wan/hdlc_x25.c b/drivers/net/wan/hdlc_x25.c
new file mode 100644
index 00000000000..07e5eef1fe0
--- /dev/null
+++ b/drivers/net/wan/hdlc_x25.c
@@ -0,0 +1,219 @@
+/*
+ * Generic HDLC support routines for Linux
+ * X.25 support
+ *
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/errno.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/pkt_sched.h>
+#include <linux/inetdevice.h>
+#include <linux/lapb.h>
+#include <linux/rtnetlink.h>
+#include <linux/hdlc.h>
+
+#include <net/x25device.h>
+
+/* These functions are callbacks called by LAPB layer */
+
+static void x25_connect_disconnect(struct net_device *dev, int reason, int code)
+{
+ struct sk_buff *skb;
+ unsigned char *ptr;
+
+ if ((skb = dev_alloc_skb(1)) == NULL) {
+ printk(KERN_ERR "%s: out of memory\n", dev->name);
+ return;
+ }
+
+ ptr = skb_put(skb, 1);
+ *ptr = code;
+
+ skb->protocol = x25_type_trans(skb, dev);
+ netif_rx(skb);
+}
+
+
+
+static void x25_connected(struct net_device *dev, int reason)
+{
+ x25_connect_disconnect(dev, reason, 1);
+}
+
+
+
+static void x25_disconnected(struct net_device *dev, int reason)
+{
+ x25_connect_disconnect(dev, reason, 2);
+}
+
+
+
+static int x25_data_indication(struct net_device *dev, struct sk_buff *skb)
+{
+ unsigned char *ptr;
+
+ skb_push(skb, 1);
+
+ if (skb_cow(skb, 1))
+ return NET_RX_DROP;
+
+ ptr = skb->data;
+ *ptr = 0;
+
+ skb->protocol = x25_type_trans(skb, dev);
+ return netif_rx(skb);
+}
+
+
+
+static void x25_data_transmit(struct net_device *dev, struct sk_buff *skb)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ hdlc->xmit(skb, dev); /* Ignore return value :-( */
+}
+
+
+
+static int x25_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ int result;
+
+
+ /* X.25 to LAPB */
+ switch (skb->data[0]) {
+ case 0: /* Data to be transmitted */
+ skb_pull(skb, 1);
+ if ((result = lapb_data_request(dev, skb)) != LAPB_OK)
+ dev_kfree_skb(skb);
+ return 0;
+
+ case 1:
+ if ((result = lapb_connect_request(dev))!= LAPB_OK) {
+ if (result == LAPB_CONNECTED)
+ /* Send connect confirm. msg to level 3 */
+ x25_connected(dev, 0);
+ else
+ printk(KERN_ERR "%s: LAPB connect request "
+ "failed, error code = %i\n",
+ dev->name, result);
+ }
+ break;
+
+ case 2:
+ if ((result = lapb_disconnect_request(dev)) != LAPB_OK) {
+ if (result == LAPB_NOTCONNECTED)
+ /* Send disconnect confirm. msg to level 3 */
+ x25_disconnected(dev, 0);
+ else
+ printk(KERN_ERR "%s: LAPB disconnect request "
+ "failed, error code = %i\n",
+ dev->name, result);
+ }
+ break;
+
+ default: /* to be defined */
+ break;
+ }
+
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+
+
+static int x25_open(struct net_device *dev)
+{
+ struct lapb_register_struct cb;
+ int result;
+
+ cb.connect_confirmation = x25_connected;
+ cb.connect_indication = x25_connected;
+ cb.disconnect_confirmation = x25_disconnected;
+ cb.disconnect_indication = x25_disconnected;
+ cb.data_indication = x25_data_indication;
+ cb.data_transmit = x25_data_transmit;
+
+ result = lapb_register(dev, &cb);
+ if (result != LAPB_OK)
+ return result;
+ return 0;
+}
+
+
+
+static void x25_close(struct net_device *dev)
+{
+ lapb_unregister(dev);
+}
+
+
+
+static int x25_rx(struct sk_buff *skb)
+{
+ hdlc_device *hdlc = dev_to_hdlc(skb->dev);
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
+ hdlc->stats.rx_dropped++;
+ return NET_RX_DROP;
+ }
+
+ if (lapb_data_received(skb->dev, skb) == LAPB_OK)
+ return NET_RX_SUCCESS;
+
+ hdlc->stats.rx_errors++;
+ dev_kfree_skb_any(skb);
+ return NET_RX_DROP;
+}
+
+
+
+int hdlc_x25_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ int result;
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_PROTO:
+ ifr->ifr_settings.type = IF_PROTO_X25;
+ return 0; /* return protocol only, no settable parameters */
+
+ case IF_PROTO_X25:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if(dev->flags & IFF_UP)
+ return -EBUSY;
+
+ result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
+ if (result)
+ return result;
+
+ hdlc_proto_detach(hdlc);
+ memset(&hdlc->proto, 0, sizeof(hdlc->proto));
+
+ hdlc->proto.open = x25_open;
+ hdlc->proto.close = x25_close;
+ hdlc->proto.netif_rx = x25_rx;
+ hdlc->proto.type_trans = NULL;
+ hdlc->proto.id = IF_PROTO_X25;
+ dev->hard_start_xmit = x25_xmit;
+ dev->hard_header = NULL;
+ dev->type = ARPHRD_X25;
+ dev->addr_len = 0;
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/drivers/net/wan/hostess_sv11.c b/drivers/net/wan/hostess_sv11.c
new file mode 100644
index 00000000000..7db1d1d0bb3
--- /dev/null
+++ b/drivers/net/wan/hostess_sv11.c
@@ -0,0 +1,420 @@
+/*
+ * Comtrol SV11 card driver
+ *
+ * This is a slightly odd Z85230 synchronous driver. All you need to
+ * know basically is
+ *
+ * Its a genuine Z85230
+ *
+ * It supports DMA using two DMA channels in SYNC mode. The driver doesn't
+ * use these facilities
+ *
+ * The control port is at io+1, the data at io+3 and turning off the DMA
+ * is done by writing 0 to io+4
+ *
+ * The hardware does the bus handling to avoid the need for delays between
+ * touching control registers.
+ *
+ * Port B isnt wired (why - beats me)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+#include <net/syncppp.h>
+#include "z85230.h"
+
+static int dma;
+
+struct sv11_device
+{
+ void *if_ptr; /* General purpose pointer (used by SPPP) */
+ struct z8530_dev sync;
+ struct ppp_device netdev;
+};
+
+/*
+ * Network driver support routines
+ */
+
+/*
+ * Frame receive. Simple for our card as we do sync ppp and there
+ * is no funny garbage involved
+ */
+
+static void hostess_input(struct z8530_channel *c, struct sk_buff *skb)
+{
+ /* Drop the CRC - it's not a good idea to try and negotiate it ;) */
+ skb_trim(skb, skb->len-2);
+ skb->protocol=__constant_htons(ETH_P_WAN_PPP);
+ skb->mac.raw=skb->data;
+ skb->dev=c->netdevice;
+ /*
+ * Send it to the PPP layer. We don't have time to process
+ * it right now.
+ */
+ netif_rx(skb);
+ c->netdevice->last_rx = jiffies;
+}
+
+/*
+ * We've been placed in the UP state
+ */
+
+static int hostess_open(struct net_device *d)
+{
+ struct sv11_device *sv11=d->priv;
+ int err = -1;
+
+ /*
+ * Link layer up
+ */
+ switch(dma)
+ {
+ case 0:
+ err=z8530_sync_open(d, &sv11->sync.chanA);
+ break;
+ case 1:
+ err=z8530_sync_dma_open(d, &sv11->sync.chanA);
+ break;
+ case 2:
+ err=z8530_sync_txdma_open(d, &sv11->sync.chanA);
+ break;
+ }
+
+ if(err)
+ return err;
+ /*
+ * Begin PPP
+ */
+ err=sppp_open(d);
+ if(err)
+ {
+ switch(dma)
+ {
+ case 0:
+ z8530_sync_close(d, &sv11->sync.chanA);
+ break;
+ case 1:
+ z8530_sync_dma_close(d, &sv11->sync.chanA);
+ break;
+ case 2:
+ z8530_sync_txdma_close(d, &sv11->sync.chanA);
+ break;
+ }
+ return err;
+ }
+ sv11->sync.chanA.rx_function=hostess_input;
+
+ /*
+ * Go go go
+ */
+
+ netif_start_queue(d);
+ return 0;
+}
+
+static int hostess_close(struct net_device *d)
+{
+ struct sv11_device *sv11=d->priv;
+ /*
+ * Discard new frames
+ */
+ sv11->sync.chanA.rx_function=z8530_null_rx;
+ /*
+ * PPP off
+ */
+ sppp_close(d);
+ /*
+ * Link layer down
+ */
+ netif_stop_queue(d);
+
+ switch(dma)
+ {
+ case 0:
+ z8530_sync_close(d, &sv11->sync.chanA);
+ break;
+ case 1:
+ z8530_sync_dma_close(d, &sv11->sync.chanA);
+ break;
+ case 2:
+ z8530_sync_txdma_close(d, &sv11->sync.chanA);
+ break;
+ }
+ return 0;
+}
+
+static int hostess_ioctl(struct net_device *d, struct ifreq *ifr, int cmd)
+{
+ /* struct sv11_device *sv11=d->priv;
+ z8530_ioctl(d,&sv11->sync.chanA,ifr,cmd) */
+ return sppp_do_ioctl(d, ifr,cmd);
+}
+
+static struct net_device_stats *hostess_get_stats(struct net_device *d)
+{
+ struct sv11_device *sv11=d->priv;
+ if(sv11)
+ return z8530_get_stats(&sv11->sync.chanA);
+ else
+ return NULL;
+}
+
+/*
+ * Passed PPP frames, fire them downwind.
+ */
+
+static int hostess_queue_xmit(struct sk_buff *skb, struct net_device *d)
+{
+ struct sv11_device *sv11=d->priv;
+ return z8530_queue_xmit(&sv11->sync.chanA, skb);
+}
+
+static int hostess_neigh_setup(struct neighbour *n)
+{
+ if (n->nud_state == NUD_NONE) {
+ n->ops = &arp_broken_ops;
+ n->output = n->ops->output;
+ }
+ return 0;
+}
+
+static int hostess_neigh_setup_dev(struct net_device *dev, struct neigh_parms *p)
+{
+ if (p->tbl->family == AF_INET) {
+ p->neigh_setup = hostess_neigh_setup;
+ p->ucast_probes = 0;
+ p->mcast_probes = 0;
+ }
+ return 0;
+}
+
+static void sv11_setup(struct net_device *dev)
+{
+ dev->open = hostess_open;
+ dev->stop = hostess_close;
+ dev->hard_start_xmit = hostess_queue_xmit;
+ dev->get_stats = hostess_get_stats;
+ dev->do_ioctl = hostess_ioctl;
+ dev->neigh_setup = hostess_neigh_setup_dev;
+}
+
+/*
+ * Description block for a Comtrol Hostess SV11 card
+ */
+
+static struct sv11_device *sv11_init(int iobase, int irq)
+{
+ struct z8530_dev *dev;
+ struct sv11_device *sv;
+
+ /*
+ * Get the needed I/O space
+ */
+
+ if(!request_region(iobase, 8, "Comtrol SV11"))
+ {
+ printk(KERN_WARNING "hostess: I/O 0x%X already in use.\n", iobase);
+ return NULL;
+ }
+
+ sv=(struct sv11_device *)kmalloc(sizeof(struct sv11_device), GFP_KERNEL);
+ if(!sv)
+ goto fail3;
+
+ memset(sv, 0, sizeof(*sv));
+ sv->if_ptr=&sv->netdev;
+
+ sv->netdev.dev = alloc_netdev(0, "hdlc%d", sv11_setup);
+ if(!sv->netdev.dev)
+ goto fail2;
+
+ SET_MODULE_OWNER(sv->netdev.dev);
+
+ dev=&sv->sync;
+
+ /*
+ * Stuff in the I/O addressing
+ */
+
+ dev->active = 0;
+
+ dev->chanA.ctrlio=iobase+1;
+ dev->chanA.dataio=iobase+3;
+ dev->chanB.ctrlio=-1;
+ dev->chanB.dataio=-1;
+ dev->chanA.irqs=&z8530_nop;
+ dev->chanB.irqs=&z8530_nop;
+
+ outb(0, iobase+4); /* DMA off */
+
+ /* We want a fast IRQ for this device. Actually we'd like an even faster
+ IRQ ;) - This is one driver RtLinux is made for */
+
+ if(request_irq(irq, &z8530_interrupt, SA_INTERRUPT, "Hostess SV11", dev)<0)
+ {
+ printk(KERN_WARNING "hostess: IRQ %d already in use.\n", irq);
+ goto fail1;
+ }
+
+ dev->irq=irq;
+ dev->chanA.private=sv;
+ dev->chanA.netdevice=sv->netdev.dev;
+ dev->chanA.dev=dev;
+ dev->chanB.dev=dev;
+
+ if(dma)
+ {
+ /*
+ * You can have DMA off or 1 and 3 thats the lot
+ * on the Comtrol.
+ */
+ dev->chanA.txdma=3;
+ dev->chanA.rxdma=1;
+ outb(0x03|0x08, iobase+4); /* DMA on */
+ if(request_dma(dev->chanA.txdma, "Hostess SV/11 (TX)")!=0)
+ goto fail;
+
+ if(dma==1)
+ {
+ if(request_dma(dev->chanA.rxdma, "Hostess SV/11 (RX)")!=0)
+ goto dmafail;
+ }
+ }
+
+ /* Kill our private IRQ line the hostess can end up chattering
+ until the configuration is set */
+ disable_irq(irq);
+
+ /*
+ * Begin normal initialise
+ */
+
+ if(z8530_init(dev)!=0)
+ {
+ printk(KERN_ERR "Z8530 series device not found.\n");
+ enable_irq(irq);
+ goto dmafail2;
+ }
+ z8530_channel_load(&dev->chanB, z8530_dead_port);
+ if(dev->type==Z85C30)
+ z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream);
+ else
+ z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream_85230);
+
+ enable_irq(irq);
+
+
+ /*
+ * Now we can take the IRQ
+ */
+ if(dev_alloc_name(dev->chanA.netdevice,"hdlc%d")>=0)
+ {
+ struct net_device *d=dev->chanA.netdevice;
+
+ /*
+ * Initialise the PPP components
+ */
+ sppp_attach(&sv->netdev);
+
+ /*
+ * Local fields
+ */
+
+ d->base_addr = iobase;
+ d->irq = irq;
+ d->priv = sv;
+
+ if(register_netdev(d))
+ {
+ printk(KERN_ERR "%s: unable to register device.\n",
+ d->name);
+ sppp_detach(d);
+ goto dmafail2;
+ }
+
+ z8530_describe(dev, "I/O", iobase);
+ dev->active=1;
+ return sv;
+ }
+dmafail2:
+ if(dma==1)
+ free_dma(dev->chanA.rxdma);
+dmafail:
+ if(dma)
+ free_dma(dev->chanA.txdma);
+fail:
+ free_irq(irq, dev);
+fail1:
+ free_netdev(sv->netdev.dev);
+fail2:
+ kfree(sv);
+fail3:
+ release_region(iobase,8);
+ return NULL;
+}
+
+static void sv11_shutdown(struct sv11_device *dev)
+{
+ sppp_detach(dev->netdev.dev);
+ unregister_netdev(dev->netdev.dev);
+ z8530_shutdown(&dev->sync);
+ free_irq(dev->sync.irq, dev);
+ if(dma)
+ {
+ if(dma==1)
+ free_dma(dev->sync.chanA.rxdma);
+ free_dma(dev->sync.chanA.txdma);
+ }
+ release_region(dev->sync.chanA.ctrlio-1, 8);
+ free_netdev(dev->netdev.dev);
+ kfree(dev);
+}
+
+#ifdef MODULE
+
+static int io=0x200;
+static int irq=9;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "The I/O base of the Comtrol Hostess SV11 card");
+module_param(dma, int, 0);
+MODULE_PARM_DESC(dma, "Set this to 1 to use DMA1/DMA3 for TX/RX");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "The interrupt line setting for the Comtrol Hostess SV11 card");
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Modular driver for the Comtrol Hostess SV11");
+
+static struct sv11_device *sv11_unit;
+
+int init_module(void)
+{
+ printk(KERN_INFO "SV-11 Z85230 Synchronous Driver v 0.03.\n");
+ printk(KERN_INFO "(c) Copyright 2001, Red Hat Inc.\n");
+ if((sv11_unit=sv11_init(io,irq))==NULL)
+ return -ENODEV;
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ if(sv11_unit)
+ sv11_shutdown(sv11_unit);
+}
+
+#endif
+
diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c
new file mode 100644
index 00000000000..7f2e3653c5e
--- /dev/null
+++ b/drivers/net/wan/lapbether.c
@@ -0,0 +1,465 @@
+/*
+ * "LAPB via ethernet" driver release 001
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This is a "pseudo" network driver to allow LAPB over Ethernet.
+ *
+ * This driver can use any ethernet destination address, and can be
+ * limited to accept frames from one dedicated ethernet card only.
+ *
+ * History
+ * LAPBETH 001 Jonathan Naylor Cloned from bpqether.c
+ * 2000-10-29 Henner Eisen lapb_data_indication() return status.
+ * 2000-11-14 Henner Eisen dev_hold/put, NETDEV_GOING_DOWN support
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/stat.h>
+#include <linux/netfilter.h>
+#include <linux/module.h>
+#include <linux/lapb.h>
+#include <linux/init.h>
+
+#include <net/x25device.h>
+
+static char bcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+/* If this number is made larger, check that the temporary string buffer
+ * in lapbeth_new_device is large enough to store the probe device name.*/
+#define MAXLAPBDEV 100
+
+struct lapbethdev {
+ struct list_head node;
+ struct net_device *ethdev; /* link to ethernet device */
+ struct net_device *axdev; /* lapbeth device (lapb#) */
+ struct net_device_stats stats; /* some statistics */
+};
+
+static struct list_head lapbeth_devices = LIST_HEAD_INIT(lapbeth_devices);
+
+/* ------------------------------------------------------------------------ */
+
+/*
+ * Get the LAPB device for the ethernet device
+ */
+static struct lapbethdev *lapbeth_get_x25_dev(struct net_device *dev)
+{
+ struct lapbethdev *lapbeth;
+
+ list_for_each_entry_rcu(lapbeth, &lapbeth_devices, node) {
+ if (lapbeth->ethdev == dev)
+ return lapbeth;
+ }
+ return NULL;
+}
+
+static __inline__ int dev_is_ethdev(struct net_device *dev)
+{
+ return dev->type == ARPHRD_ETHER && strncmp(dev->name, "dummy", 5);
+}
+
+/* ------------------------------------------------------------------------ */
+
+/*
+ * Receive a LAPB frame via an ethernet interface.
+ */
+static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype)
+{
+ int len, err;
+ struct lapbethdev *lapbeth;
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ return NET_RX_DROP;
+
+ if (!pskb_may_pull(skb, 2))
+ goto drop;
+
+ rcu_read_lock();
+ lapbeth = lapbeth_get_x25_dev(dev);
+ if (!lapbeth)
+ goto drop_unlock;
+ if (!netif_running(lapbeth->axdev))
+ goto drop_unlock;
+
+ lapbeth->stats.rx_packets++;
+
+ len = skb->data[0] + skb->data[1] * 256;
+ lapbeth->stats.rx_bytes += len;
+
+ skb_pull(skb, 2); /* Remove the length bytes */
+ skb_trim(skb, len); /* Set the length of the data */
+
+ if ((err = lapb_data_received(lapbeth->axdev, skb)) != LAPB_OK) {
+ printk(KERN_DEBUG "lapbether: lapb_data_received err - %d\n", err);
+ goto drop_unlock;
+ }
+out:
+ rcu_read_unlock();
+ return 0;
+drop_unlock:
+ kfree_skb(skb);
+ goto out;
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int lapbeth_data_indication(struct net_device *dev, struct sk_buff *skb)
+{
+ unsigned char *ptr;
+
+ skb_push(skb, 1);
+
+ if (skb_cow(skb, 1))
+ return NET_RX_DROP;
+
+ ptr = skb->data;
+ *ptr = 0x00;
+
+ skb->protocol = x25_type_trans(skb, dev);
+ skb->dev->last_rx = jiffies;
+ return netif_rx(skb);
+}
+
+/*
+ * Send a LAPB frame via an ethernet interface
+ */
+static int lapbeth_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ int err = -ENODEV;
+
+ /*
+ * Just to be *really* sure not to send anything if the interface
+ * is down, the ethernet device may have gone.
+ */
+ if (!netif_running(dev)) {
+ goto drop;
+ }
+
+ switch (skb->data[0]) {
+ case 0x00:
+ err = 0;
+ break;
+ case 0x01:
+ if ((err = lapb_connect_request(dev)) != LAPB_OK)
+ printk(KERN_ERR "lapbeth: lapb_connect_request "
+ "error: %d\n", err);
+ goto drop_ok;
+ case 0x02:
+ if ((err = lapb_disconnect_request(dev)) != LAPB_OK)
+ printk(KERN_ERR "lapbeth: lapb_disconnect_request "
+ "err: %d\n", err);
+ /* Fall thru */
+ default:
+ goto drop_ok;
+ }
+
+ skb_pull(skb, 1);
+
+ if ((err = lapb_data_request(dev, skb)) != LAPB_OK) {
+ printk(KERN_ERR "lapbeth: lapb_data_request error - %d\n", err);
+ err = -ENOMEM;
+ goto drop;
+ }
+ err = 0;
+out:
+ return err;
+drop_ok:
+ err = 0;
+drop:
+ kfree_skb(skb);
+ goto out;
+}
+
+static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb)
+{
+ struct lapbethdev *lapbeth = netdev_priv(ndev);
+ unsigned char *ptr;
+ struct net_device *dev;
+ int size = skb->len;
+
+ skb->protocol = htons(ETH_P_X25);
+
+ ptr = skb_push(skb, 2);
+
+ *ptr++ = size % 256;
+ *ptr++ = size / 256;
+
+ lapbeth->stats.tx_packets++;
+ lapbeth->stats.tx_bytes += size;
+
+ skb->dev = dev = lapbeth->ethdev;
+
+ dev->hard_header(skb, dev, ETH_P_DEC, bcast_addr, NULL, 0);
+
+ dev_queue_xmit(skb);
+}
+
+static void lapbeth_connected(struct net_device *dev, int reason)
+{
+ unsigned char *ptr;
+ struct sk_buff *skb = dev_alloc_skb(1);
+
+ if (!skb) {
+ printk(KERN_ERR "lapbeth: out of memory\n");
+ return;
+ }
+
+ ptr = skb_put(skb, 1);
+ *ptr = 0x01;
+
+ skb->protocol = x25_type_trans(skb, dev);
+ skb->dev->last_rx = jiffies;
+ netif_rx(skb);
+}
+
+static void lapbeth_disconnected(struct net_device *dev, int reason)
+{
+ unsigned char *ptr;
+ struct sk_buff *skb = dev_alloc_skb(1);
+
+ if (!skb) {
+ printk(KERN_ERR "lapbeth: out of memory\n");
+ return;
+ }
+
+ ptr = skb_put(skb, 1);
+ *ptr = 0x02;
+
+ skb->protocol = x25_type_trans(skb, dev);
+ skb->dev->last_rx = jiffies;
+ netif_rx(skb);
+}
+
+/*
+ * Statistics
+ */
+static struct net_device_stats *lapbeth_get_stats(struct net_device *dev)
+{
+ struct lapbethdev *lapbeth = netdev_priv(dev);
+ return &lapbeth->stats;
+}
+
+/*
+ * Set AX.25 callsign
+ */
+static int lapbeth_set_mac_address(struct net_device *dev, void *addr)
+{
+ struct sockaddr *sa = addr;
+ memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+ return 0;
+}
+
+
+static struct lapb_register_struct lapbeth_callbacks = {
+ .connect_confirmation = lapbeth_connected,
+ .connect_indication = lapbeth_connected,
+ .disconnect_confirmation = lapbeth_disconnected,
+ .disconnect_indication = lapbeth_disconnected,
+ .data_indication = lapbeth_data_indication,
+ .data_transmit = lapbeth_data_transmit,
+
+};
+
+/*
+ * open/close a device
+ */
+static int lapbeth_open(struct net_device *dev)
+{
+ int err;
+
+ if ((err = lapb_register(dev, &lapbeth_callbacks)) != LAPB_OK) {
+ printk(KERN_ERR "lapbeth: lapb_register error - %d\n", err);
+ return -ENODEV;
+ }
+
+ netif_start_queue(dev);
+ return 0;
+}
+
+static int lapbeth_close(struct net_device *dev)
+{
+ int err;
+
+ netif_stop_queue(dev);
+
+ if ((err = lapb_unregister(dev)) != LAPB_OK)
+ printk(KERN_ERR "lapbeth: lapb_unregister error - %d\n", err);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static void lapbeth_setup(struct net_device *dev)
+{
+ dev->hard_start_xmit = lapbeth_xmit;
+ dev->open = lapbeth_open;
+ dev->stop = lapbeth_close;
+ dev->destructor = free_netdev;
+ dev->set_mac_address = lapbeth_set_mac_address;
+ dev->get_stats = lapbeth_get_stats;
+ dev->type = ARPHRD_X25;
+ dev->hard_header_len = 3;
+ dev->mtu = 1000;
+ dev->addr_len = 0;
+ SET_MODULE_OWNER(dev);
+}
+
+/*
+ * Setup a new device.
+ */
+static int lapbeth_new_device(struct net_device *dev)
+{
+ struct net_device *ndev;
+ struct lapbethdev *lapbeth;
+ int rc = -ENOMEM;
+
+ ASSERT_RTNL();
+
+ ndev = alloc_netdev(sizeof(*lapbeth), "lapb%d",
+ lapbeth_setup);
+ if (!ndev)
+ goto out;
+
+ lapbeth = netdev_priv(ndev);
+ lapbeth->axdev = ndev;
+
+ dev_hold(dev);
+ lapbeth->ethdev = dev;
+
+ rc = dev_alloc_name(ndev, ndev->name);
+ if (rc < 0)
+ goto fail;
+
+ rc = -EIO;
+ if (register_netdevice(ndev))
+ goto fail;
+
+ list_add_rcu(&lapbeth->node, &lapbeth_devices);
+ rc = 0;
+out:
+ return rc;
+fail:
+ dev_put(dev);
+ free_netdev(ndev);
+ kfree(lapbeth);
+ goto out;
+}
+
+/*
+ * Free a lapb network device.
+ */
+static void lapbeth_free_device(struct lapbethdev *lapbeth)
+{
+ dev_put(lapbeth->ethdev);
+ list_del_rcu(&lapbeth->node);
+ unregister_netdevice(lapbeth->axdev);
+}
+
+/*
+ * Handle device status changes.
+ *
+ * Called from notifier with RTNL held.
+ */
+static int lapbeth_device_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct lapbethdev *lapbeth;
+ struct net_device *dev = ptr;
+
+ if (!dev_is_ethdev(dev))
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_UP:
+ /* New ethernet device -> new LAPB interface */
+ if (lapbeth_get_x25_dev(dev) == NULL)
+ lapbeth_new_device(dev);
+ break;
+ case NETDEV_DOWN:
+ /* ethernet device closed -> close LAPB interface */
+ lapbeth = lapbeth_get_x25_dev(dev);
+ if (lapbeth)
+ dev_close(lapbeth->axdev);
+ break;
+ case NETDEV_UNREGISTER:
+ /* ethernet device disappears -> remove LAPB interface */
+ lapbeth = lapbeth_get_x25_dev(dev);
+ if (lapbeth)
+ lapbeth_free_device(lapbeth);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static struct packet_type lapbeth_packet_type = {
+ .type = __constant_htons(ETH_P_DEC),
+ .func = lapbeth_rcv,
+};
+
+static struct notifier_block lapbeth_dev_notifier = {
+ .notifier_call = lapbeth_device_event,
+};
+
+static char banner[] __initdata = KERN_INFO "LAPB Ethernet driver version 0.02\n";
+
+static int __init lapbeth_init_driver(void)
+{
+ dev_add_pack(&lapbeth_packet_type);
+
+ register_netdevice_notifier(&lapbeth_dev_notifier);
+
+ printk(banner);
+
+ return 0;
+}
+module_init(lapbeth_init_driver);
+
+static void __exit lapbeth_cleanup_driver(void)
+{
+ struct lapbethdev *lapbeth;
+ struct list_head *entry, *tmp;
+
+ dev_remove_pack(&lapbeth_packet_type);
+ unregister_netdevice_notifier(&lapbeth_dev_notifier);
+
+ rtnl_lock();
+ list_for_each_safe(entry, tmp, &lapbeth_devices) {
+ lapbeth = list_entry(entry, struct lapbethdev, node);
+
+ unregister_netdevice(lapbeth->axdev);
+ }
+ rtnl_unlock();
+}
+module_exit(lapbeth_cleanup_driver);
+
+MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The unofficial LAPB over Ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/lmc/Makefile b/drivers/net/wan/lmc/Makefile
new file mode 100644
index 00000000000..dabdcfed4ef
--- /dev/null
+++ b/drivers/net/wan/lmc/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for the Lan Media 21140 based WAN cards
+# Specifically the 1000,1200,5200,5245
+#
+
+obj-$(CONFIG_LANMEDIA) += lmc.o
+
+lmc-objs := lmc_debug.o lmc_media.o lmc_main.o lmc_proto.o
+
+# Like above except every packet gets echoed to KERN_DEBUG
+# in hex
+#
+# DBDEF = \
+# -DDEBUG \
+# -DLMC_PACKET_LOG
+
+EXTRA_CFLAGS += -I. $(DBGDEF)
diff --git a/drivers/net/wan/lmc/lmc.h b/drivers/net/wan/lmc/lmc.h
new file mode 100644
index 00000000000..882e58c1bfd
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc.h
@@ -0,0 +1,33 @@
+#ifndef _LMC_H_
+#define _LMC_H_
+
+#include "lmc_var.h"
+
+/*
+ * prototypes for everyone
+ */
+int lmc_probe(struct net_device * dev);
+unsigned lmc_mii_readreg(lmc_softc_t * const sc, unsigned
+ devaddr, unsigned regno);
+void lmc_mii_writereg(lmc_softc_t * const sc, unsigned devaddr,
+ unsigned regno, unsigned data);
+void lmc_led_on(lmc_softc_t * const, u_int32_t);
+void lmc_led_off(lmc_softc_t * const, u_int32_t);
+unsigned lmc_mii_readreg(lmc_softc_t * const, unsigned, unsigned);
+void lmc_mii_writereg(lmc_softc_t * const, unsigned, unsigned, unsigned);
+void lmc_gpio_mkinput(lmc_softc_t * const sc, u_int32_t bits);
+void lmc_gpio_mkoutput(lmc_softc_t * const sc, u_int32_t bits);
+
+int lmc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
+
+extern lmc_media_t lmc_ds3_media;
+extern lmc_media_t lmc_ssi_media;
+extern lmc_media_t lmc_t1_media;
+extern lmc_media_t lmc_hssi_media;
+
+#ifdef _DBG_EVENTLOG
+static void lmcEventLog( u_int32_t EventNum, u_int32_t arg2, u_int32_t arg3 );
+#endif
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_debug.c b/drivers/net/wan/lmc/lmc_debug.c
new file mode 100644
index 00000000000..9dccd9546a1
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_debug.c
@@ -0,0 +1,85 @@
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+
+#include "lmc_debug.h"
+
+/*
+ * Prints out len, max to 80 octets using printk, 20 per line
+ */
+void lmcConsoleLog(char *type, unsigned char *ucData, int iLen)
+{
+#ifdef DEBUG
+#ifdef LMC_PACKET_LOG
+ int iNewLine = 1;
+ char str[80], *pstr;
+
+ sprintf(str, KERN_DEBUG "lmc: %s: ", type);
+ pstr = str+strlen(str);
+
+ if(iLen > 240){
+ printk(KERN_DEBUG "lmc: Printing 240 chars... out of: %d\n", iLen);
+ iLen = 240;
+ }
+ else{
+ printk(KERN_DEBUG "lmc: Printing %d chars\n", iLen);
+ }
+
+ while(iLen > 0)
+ {
+ sprintf(pstr, "%02x ", *ucData);
+ pstr+=3;
+ ucData++;
+ if( !(iNewLine % 20))
+ {
+ sprintf(pstr, "\n");
+ printk(str);
+ sprintf(str, KERN_DEBUG "lmc: %s: ", type);
+ pstr=str+strlen(str);
+ }
+ iNewLine++;
+ iLen--;
+ }
+ sprintf(pstr, "\n");
+ printk(str);
+#endif
+#endif
+}
+
+#ifdef DEBUG
+u_int32_t lmcEventLogIndex = 0;
+u_int32_t lmcEventLogBuf[LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS];
+#endif
+
+void lmcEventLog (u_int32_t EventNum, u_int32_t arg2, u_int32_t arg3)
+{
+#ifdef DEBUG
+ lmcEventLogBuf[lmcEventLogIndex++] = EventNum;
+ lmcEventLogBuf[lmcEventLogIndex++] = arg2;
+ lmcEventLogBuf[lmcEventLogIndex++] = arg3;
+ lmcEventLogBuf[lmcEventLogIndex++] = jiffies;
+
+ lmcEventLogIndex &= (LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS) - 1;
+#endif
+}
+
+void lmc_trace(struct net_device *dev, char *msg){
+#ifdef LMC_TRACE
+ unsigned long j = jiffies + 3; /* Wait for 50 ms */
+
+ if(in_interrupt()){
+ printk("%s: * %s\n", dev->name, msg);
+// while(time_before(jiffies, j+10))
+// ;
+ }
+ else {
+ printk("%s: %s\n", dev->name, msg);
+ while(time_before(jiffies, j))
+ schedule();
+ }
+#endif
+}
+
+
+/* --------------------------- end if_lmc_linux.c ------------------------ */
diff --git a/drivers/net/wan/lmc/lmc_debug.h b/drivers/net/wan/lmc/lmc_debug.h
new file mode 100644
index 00000000000..cf3563859bf
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_debug.h
@@ -0,0 +1,52 @@
+#ifndef _LMC_DEBUG_H_
+#define _LMC_DEBUG_H_
+
+#ifdef DEBUG
+#ifdef LMC_PACKET_LOG
+#define LMC_CONSOLE_LOG(x,y,z) lmcConsoleLog((x), (y), (z))
+#else
+#define LMC_CONSOLE_LOG(x,y,z)
+#endif
+#else
+#define LMC_CONSOLE_LOG(x,y,z)
+#endif
+
+
+
+/* Debug --- Event log definitions --- */
+/* EVENTLOGSIZE*EVENTLOGARGS needs to be a power of 2 */
+#define LMC_EVENTLOGSIZE 1024 /* number of events in eventlog */
+#define LMC_EVENTLOGARGS 4 /* number of args for each event */
+
+/* event indicators */
+#define LMC_EVENT_XMT 1
+#define LMC_EVENT_XMTEND 2
+#define LMC_EVENT_XMTINT 3
+#define LMC_EVENT_RCVINT 4
+#define LMC_EVENT_RCVEND 5
+#define LMC_EVENT_INT 6
+#define LMC_EVENT_XMTINTTMO 7
+#define LMC_EVENT_XMTPRCTMO 8
+#define LMC_EVENT_INTEND 9
+#define LMC_EVENT_RESET1 10
+#define LMC_EVENT_RESET2 11
+#define LMC_EVENT_FORCEDRESET 12
+#define LMC_EVENT_WATCHDOG 13
+#define LMC_EVENT_BADPKTSURGE 14
+#define LMC_EVENT_TBUSY0 15
+#define LMC_EVENT_TBUSY1 16
+
+
+#ifdef DEBUG
+extern u_int32_t lmcEventLogIndex;
+extern u_int32_t lmcEventLogBuf[LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS];
+#define LMC_EVENT_LOG(x, y, z) lmcEventLog((x), (y), (z))
+#else
+#define LMC_EVENT_LOG(x,y,z)
+#endif /* end ifdef _DBG_EVENTLOG */
+
+void lmcConsoleLog(char *type, unsigned char *ucData, int iLen);
+void lmcEventLog (u_int32_t EventNum, u_int32_t arg2, u_int32_t arg3);
+void lmc_trace(struct net_device *dev, char *msg);
+
+#endif
diff --git a/drivers/net/wan/lmc/lmc_ioctl.h b/drivers/net/wan/lmc/lmc_ioctl.h
new file mode 100644
index 00000000000..57dd861cd3d
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_ioctl.h
@@ -0,0 +1,257 @@
+#ifndef _LMC_IOCTL_H_
+#define _LMC_IOCTL_H_
+/* $Id: lmc_ioctl.h,v 1.15 2000/04/06 12:16:43 asj Exp $ */
+
+ /*
+ * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+ * All rights reserved. www.lanmedia.com
+ *
+ * This code is written by:
+ * Andrew Stanley-Jones (asj@cban.com)
+ * Rob Braun (bbraun@vix.com),
+ * Michael Graff (explorer@vix.com) and
+ * Matt Thomas (matt@3am-software.com).
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License version 2, incorporated herein by reference.
+ */
+
+#define LMCIOCGINFO SIOCDEVPRIVATE+3 /* get current state */
+#define LMCIOCSINFO SIOCDEVPRIVATE+4 /* set state to user values */
+#define LMCIOCGETLMCSTATS SIOCDEVPRIVATE+5
+#define LMCIOCCLEARLMCSTATS SIOCDEVPRIVATE+6
+#define LMCIOCDUMPEVENTLOG SIOCDEVPRIVATE+7
+#define LMCIOCGETXINFO SIOCDEVPRIVATE+8
+#define LMCIOCSETCIRCUIT SIOCDEVPRIVATE+9
+#define LMCIOCUNUSEDATM SIOCDEVPRIVATE+10
+#define LMCIOCRESET SIOCDEVPRIVATE+11
+#define LMCIOCT1CONTROL SIOCDEVPRIVATE+12
+#define LMCIOCIFTYPE SIOCDEVPRIVATE+13
+#define LMCIOCXILINX SIOCDEVPRIVATE+14
+
+#define LMC_CARDTYPE_UNKNOWN -1
+#define LMC_CARDTYPE_HSSI 1 /* probed card is a HSSI card */
+#define LMC_CARDTYPE_DS3 2 /* probed card is a DS3 card */
+#define LMC_CARDTYPE_SSI 3 /* probed card is a SSI card */
+#define LMC_CARDTYPE_T1 4 /* probed card is a T1 card */
+
+#define LMC_CTL_CARDTYPE_LMC5200 0 /* HSSI */
+#define LMC_CTL_CARDTYPE_LMC5245 1 /* DS3 */
+#define LMC_CTL_CARDTYPE_LMC1000 2 /* SSI, V.35 */
+#define LMC_CTL_CARDTYPE_LMC1200 3 /* DS1 */
+
+#define LMC_CTL_OFF 0 /* generic OFF value */
+#define LMC_CTL_ON 1 /* generic ON value */
+
+#define LMC_CTL_CLOCK_SOURCE_EXT 0 /* clock off line */
+#define LMC_CTL_CLOCK_SOURCE_INT 1 /* internal clock */
+
+#define LMC_CTL_CRC_LENGTH_16 16
+#define LMC_CTL_CRC_LENGTH_32 32
+#define LMC_CTL_CRC_BYTESIZE_2 2
+#define LMC_CTL_CRC_BYTESIZE_4 4
+
+
+#define LMC_CTL_CABLE_LENGTH_LT_100FT 0 /* DS3 cable < 100 feet */
+#define LMC_CTL_CABLE_LENGTH_GT_100FT 1 /* DS3 cable >= 100 feet */
+
+#define LMC_CTL_CIRCUIT_TYPE_E1 0
+#define LMC_CTL_CIRCUIT_TYPE_T1 1
+
+/*
+ * IFTYPE defines
+ */
+#define LMC_PPP 1 /* use sppp interface */
+#define LMC_NET 2 /* use direct net interface */
+#define LMC_RAW 3 /* use direct net interface */
+
+/*
+ * These are not in the least IOCTL related, but I want them common.
+ */
+/*
+ * assignments for the GPIO register on the DEC chip (common)
+ */
+#define LMC_GEP_INIT 0x01 /* 0: */
+#define LMC_GEP_RESET 0x02 /* 1: */
+#define LMC_GEP_MODE 0x10 /* 4: */
+#define LMC_GEP_DP 0x20 /* 5: */
+#define LMC_GEP_DATA 0x40 /* 6: serial out */
+#define LMC_GEP_CLK 0x80 /* 7: serial clock */
+
+/*
+ * HSSI GPIO assignments
+ */
+#define LMC_GEP_HSSI_ST 0x04 /* 2: receive timing sense (deprecated) */
+#define LMC_GEP_HSSI_CLOCK 0x08 /* 3: clock source */
+
+/*
+ * T1 GPIO assignments
+ */
+#define LMC_GEP_SSI_GENERATOR 0x04 /* 2: enable prog freq gen serial i/f */
+#define LMC_GEP_SSI_TXCLOCK 0x08 /* 3: provide clock on TXCLOCK output */
+
+/*
+ * Common MII16 bits
+ */
+#define LMC_MII16_LED0 0x0080
+#define LMC_MII16_LED1 0x0100
+#define LMC_MII16_LED2 0x0200
+#define LMC_MII16_LED3 0x0400 /* Error, and the red one */
+#define LMC_MII16_LED_ALL 0x0780 /* LED bit mask */
+#define LMC_MII16_FIFO_RESET 0x0800
+
+/*
+ * definitions for HSSI
+ */
+#define LMC_MII16_HSSI_TA 0x0001
+#define LMC_MII16_HSSI_CA 0x0002
+#define LMC_MII16_HSSI_LA 0x0004
+#define LMC_MII16_HSSI_LB 0x0008
+#define LMC_MII16_HSSI_LC 0x0010
+#define LMC_MII16_HSSI_TM 0x0020
+#define LMC_MII16_HSSI_CRC 0x0040
+
+/*
+ * assignments for the MII register 16 (DS3)
+ */
+#define LMC_MII16_DS3_ZERO 0x0001
+#define LMC_MII16_DS3_TRLBK 0x0002
+#define LMC_MII16_DS3_LNLBK 0x0004
+#define LMC_MII16_DS3_RAIS 0x0008
+#define LMC_MII16_DS3_TAIS 0x0010
+#define LMC_MII16_DS3_BIST 0x0020
+#define LMC_MII16_DS3_DLOS 0x0040
+#define LMC_MII16_DS3_CRC 0x1000
+#define LMC_MII16_DS3_SCRAM 0x2000
+#define LMC_MII16_DS3_SCRAM_LARS 0x4000
+
+/* Note: 2 pairs of LEDs where swapped by mistake
+ * in Xilinx code for DS3 & DS1 adapters */
+#define LMC_DS3_LED0 0x0100 /* bit 08 yellow */
+#define LMC_DS3_LED1 0x0080 /* bit 07 blue */
+#define LMC_DS3_LED2 0x0400 /* bit 10 green */
+#define LMC_DS3_LED3 0x0200 /* bit 09 red */
+
+/*
+ * framer register 0 and 7 (7 is latched and reset on read)
+ */
+#define LMC_FRAMER_REG0_DLOS 0x80 /* digital loss of service */
+#define LMC_FRAMER_REG0_OOFS 0x40 /* out of frame sync */
+#define LMC_FRAMER_REG0_AIS 0x20 /* alarm indication signal */
+#define LMC_FRAMER_REG0_CIS 0x10 /* channel idle */
+#define LMC_FRAMER_REG0_LOC 0x08 /* loss of clock */
+
+/*
+ * Framer register 9 contains the blue alarm signal
+ */
+#define LMC_FRAMER_REG9_RBLUE 0x02 /* Blue alarm failure */
+
+/*
+ * Framer register 0x10 contains xbit error
+ */
+#define LMC_FRAMER_REG10_XBIT 0x01 /* X bit error alarm failure */
+
+/*
+ * And SSI, LMC1000
+ */
+#define LMC_MII16_SSI_DTR 0x0001 /* DTR output RW */
+#define LMC_MII16_SSI_DSR 0x0002 /* DSR input RO */
+#define LMC_MII16_SSI_RTS 0x0004 /* RTS output RW */
+#define LMC_MII16_SSI_CTS 0x0008 /* CTS input RO */
+#define LMC_MII16_SSI_DCD 0x0010 /* DCD input RO */
+#define LMC_MII16_SSI_RI 0x0020 /* RI input RO */
+#define LMC_MII16_SSI_CRC 0x1000 /* CRC select - RW */
+
+/*
+ * bits 0x0080 through 0x0800 are generic, and described
+ * above with LMC_MII16_LED[0123] _LED_ALL, and _FIFO_RESET
+ */
+#define LMC_MII16_SSI_LL 0x1000 /* LL output RW */
+#define LMC_MII16_SSI_RL 0x2000 /* RL output RW */
+#define LMC_MII16_SSI_TM 0x4000 /* TM input RO */
+#define LMC_MII16_SSI_LOOP 0x8000 /* loopback enable RW */
+
+/*
+ * Some of the MII16 bits are mirrored in the MII17 register as well,
+ * but let's keep thing separate for now, and get only the cable from
+ * the MII17.
+ */
+#define LMC_MII17_SSI_CABLE_MASK 0x0038 /* mask to extract the cable type */
+#define LMC_MII17_SSI_CABLE_SHIFT 3 /* shift to extract the cable type */
+
+/*
+ * And T1, LMC1200
+ */
+#define LMC_MII16_T1_UNUSED1 0x0003
+#define LMC_MII16_T1_XOE 0x0004
+#define LMC_MII16_T1_RST 0x0008 /* T1 chip reset - RW */
+#define LMC_MII16_T1_Z 0x0010 /* output impedance T1=1, E1=0 output - RW */
+#define LMC_MII16_T1_INTR 0x0020 /* interrupt from 8370 - RO */
+#define LMC_MII16_T1_ONESEC 0x0040 /* one second square wave - ro */
+
+#define LMC_MII16_T1_LED0 0x0100
+#define LMC_MII16_T1_LED1 0x0080
+#define LMC_MII16_T1_LED2 0x0400
+#define LMC_MII16_T1_LED3 0x0200
+#define LMC_MII16_T1_FIFO_RESET 0x0800
+
+#define LMC_MII16_T1_CRC 0x1000 /* CRC select - RW */
+#define LMC_MII16_T1_UNUSED2 0xe000
+
+
+/* 8370 framer registers */
+
+#define T1FRAMER_ALARM1_STATUS 0x47
+#define T1FRAMER_ALARM2_STATUS 0x48
+#define T1FRAMER_FERR_LSB 0x50
+#define T1FRAMER_FERR_MSB 0x51 /* framing bit error counter */
+#define T1FRAMER_LCV_LSB 0x54
+#define T1FRAMER_LCV_MSB 0x55 /* line code violation counter */
+#define T1FRAMER_AERR 0x5A
+
+/* mask for the above AERR register */
+#define T1FRAMER_LOF_MASK (0x0f0) /* receive loss of frame */
+#define T1FRAMER_COFA_MASK (0x0c0) /* change of frame alignment */
+#define T1FRAMER_SEF_MASK (0x03) /* severely errored frame */
+
+/* 8370 framer register ALM1 (0x47) values
+ * used to determine link status
+ */
+
+#define T1F_SIGFRZ 0x01 /* signaling freeze */
+#define T1F_RLOF 0x02 /* receive loss of frame alignment */
+#define T1F_RLOS 0x04 /* receive loss of signal */
+#define T1F_RALOS 0x08 /* receive analog loss of signal or RCKI loss of clock */
+#define T1F_RAIS 0x10 /* receive alarm indication signal */
+#define T1F_UNUSED 0x20
+#define T1F_RYEL 0x40 /* receive yellow alarm */
+#define T1F_RMYEL 0x80 /* receive multiframe yellow alarm */
+
+#define LMC_T1F_WRITE 0
+#define LMC_T1F_READ 1
+
+typedef struct lmc_st1f_control {
+ int command;
+ int address;
+ int value;
+ char __user *data;
+} lmc_t1f_control;
+
+enum lmc_xilinx_c {
+ lmc_xilinx_reset = 1,
+ lmc_xilinx_load_prom = 2,
+ lmc_xilinx_load = 3
+};
+
+struct lmc_xilinx_control {
+ enum lmc_xilinx_c command;
+ int len;
+ char __user *data;
+};
+
+/* ------------------ end T1 defs ------------------- */
+
+#define LMC_MII_LedMask 0x0780
+#define LMC_MII_LedBitPos 7
+
+#endif
diff --git a/drivers/net/wan/lmc/lmc_main.c b/drivers/net/wan/lmc/lmc_main.c
new file mode 100644
index 00000000000..15e545f66cd
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_main.c
@@ -0,0 +1,2201 @@
+ /*
+ * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+ * All rights reserved. www.lanmedia.com
+ *
+ * This code is written by:
+ * Andrew Stanley-Jones (asj@cban.com)
+ * Rob Braun (bbraun@vix.com),
+ * Michael Graff (explorer@vix.com) and
+ * Matt Thomas (matt@3am-software.com).
+ *
+ * With Help By:
+ * David Boggs
+ * Ron Crane
+ * Alan Cox
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License version 2, incorporated herein by reference.
+ *
+ * Driver for the LanMedia LMC5200, LMC5245, LMC1000, LMC1200 cards.
+ *
+ * To control link specific options lmcctl is required.
+ * It can be obtained from ftp.lanmedia.com.
+ *
+ * Linux driver notes:
+ * Linux uses the device struct lmc_private to pass private information
+ * arround.
+ *
+ * The initialization portion of this driver (the lmc_reset() and the
+ * lmc_dec_reset() functions, as well as the led controls and the
+ * lmc_initcsrs() functions.
+ *
+ * The watchdog function runs every second and checks to see if
+ * we still have link, and that the timing source is what we expected
+ * it to be. If link is lost, the interface is marked down, and
+ * we no longer can transmit.
+ *
+ */
+
+/* $Id: lmc_main.c,v 1.36 2000/04/11 05:25:25 asj Exp $ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inet.h>
+#include <linux/bitops.h>
+
+#include <net/syncppp.h>
+
+#include <asm/processor.h> /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+//#include <asm/spinlock.h>
+
+#define DRIVER_MAJOR_VERSION 1
+#define DRIVER_MINOR_VERSION 34
+#define DRIVER_SUB_VERSION 0
+
+#define DRIVER_VERSION ((DRIVER_MAJOR_VERSION << 8) + DRIVER_MINOR_VERSION)
+
+#include "lmc.h"
+#include "lmc_var.h"
+#include "lmc_ioctl.h"
+#include "lmc_debug.h"
+#include "lmc_proto.h"
+
+static int lmc_first_load = 0;
+
+static int LMC_PKT_BUF_SZ = 1542;
+
+static struct pci_device_id lmc_pci_tbl[] = {
+ { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_FAST,
+ PCI_VENDOR_ID_LMC, PCI_ANY_ID },
+ { PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_FAST,
+ PCI_ANY_ID, PCI_VENDOR_ID_LMC },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, lmc_pci_tbl);
+MODULE_LICENSE("GPL");
+
+
+static int lmc_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static int lmc_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static int lmc_rx (struct net_device *dev);
+static int lmc_open(struct net_device *dev);
+static int lmc_close(struct net_device *dev);
+static struct net_device_stats *lmc_get_stats(struct net_device *dev);
+static irqreturn_t lmc_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
+static void lmc_initcsrs(lmc_softc_t * const sc, lmc_csrptr_t csr_base, size_t csr_size);
+static void lmc_softreset(lmc_softc_t * const);
+static void lmc_running_reset(struct net_device *dev);
+static int lmc_ifdown(struct net_device * const);
+static void lmc_watchdog(unsigned long data);
+static void lmc_reset(lmc_softc_t * const sc);
+static void lmc_dec_reset(lmc_softc_t * const sc);
+static void lmc_driver_timeout(struct net_device *dev);
+
+/*
+ * linux reserves 16 device specific IOCTLs. We call them
+ * LMCIOC* to control various bits of our world.
+ */
+int lmc_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd) /*fold00*/
+{
+ lmc_softc_t *sc;
+ lmc_ctl_t ctl;
+ int ret;
+ u_int16_t regVal;
+ unsigned long flags;
+
+ struct sppp *sp;
+
+ ret = -EOPNOTSUPP;
+
+ sc = dev->priv;
+
+ lmc_trace(dev, "lmc_ioctl in");
+
+ /*
+ * Most functions mess with the structure
+ * Disable interrupts while we do the polling
+ */
+ spin_lock_irqsave(&sc->lmc_lock, flags);
+
+ switch (cmd) {
+ /*
+ * Return current driver state. Since we keep this up
+ * To date internally, just copy this out to the user.
+ */
+ case LMCIOCGINFO: /*fold01*/
+ if (copy_to_user(ifr->ifr_data, &sc->ictl, sizeof (lmc_ctl_t)))
+ return -EFAULT;
+ ret = 0;
+ break;
+
+ case LMCIOCSINFO: /*fold01*/
+ sp = &((struct ppp_device *) dev)->sppp;
+ if (!capable(CAP_NET_ADMIN)) {
+ ret = -EPERM;
+ break;
+ }
+
+ if(dev->flags & IFF_UP){
+ ret = -EBUSY;
+ break;
+ }
+
+ if (copy_from_user(&ctl, ifr->ifr_data, sizeof (lmc_ctl_t)))
+ return -EFAULT;
+
+ sc->lmc_media->set_status (sc, &ctl);
+
+ if(ctl.crc_length != sc->ictl.crc_length) {
+ sc->lmc_media->set_crc_length(sc, ctl.crc_length);
+ if (sc->ictl.crc_length == LMC_CTL_CRC_LENGTH_16)
+ sc->TxDescriptControlInit |= LMC_TDES_ADD_CRC_DISABLE;
+ else
+ sc->TxDescriptControlInit &= ~LMC_TDES_ADD_CRC_DISABLE;
+ }
+
+ if (ctl.keepalive_onoff == LMC_CTL_OFF)
+ sp->pp_flags &= ~PP_KEEPALIVE; /* Turn off */
+ else
+ sp->pp_flags |= PP_KEEPALIVE; /* Turn on */
+
+ ret = 0;
+ break;
+
+ case LMCIOCIFTYPE: /*fold01*/
+ {
+ u_int16_t old_type = sc->if_type;
+ u_int16_t new_type;
+
+ if (!capable(CAP_NET_ADMIN)) {
+ ret = -EPERM;
+ break;
+ }
+
+ if (copy_from_user(&new_type, ifr->ifr_data, sizeof(u_int16_t)))
+ return -EFAULT;
+
+
+ if (new_type == old_type)
+ {
+ ret = 0 ;
+ break; /* no change */
+ }
+
+ lmc_proto_close(sc);
+ lmc_proto_detach(sc);
+
+ sc->if_type = new_type;
+// lmc_proto_init(sc);
+ lmc_proto_attach(sc);
+ lmc_proto_open(sc);
+
+ ret = 0 ;
+ break ;
+ }
+
+ case LMCIOCGETXINFO: /*fold01*/
+ sc->lmc_xinfo.Magic0 = 0xBEEFCAFE;
+
+ sc->lmc_xinfo.PciCardType = sc->lmc_cardtype;
+ sc->lmc_xinfo.PciSlotNumber = 0;
+ sc->lmc_xinfo.DriverMajorVersion = DRIVER_MAJOR_VERSION;
+ sc->lmc_xinfo.DriverMinorVersion = DRIVER_MINOR_VERSION;
+ sc->lmc_xinfo.DriverSubVersion = DRIVER_SUB_VERSION;
+ sc->lmc_xinfo.XilinxRevisionNumber =
+ lmc_mii_readreg (sc, 0, 3) & 0xf;
+ sc->lmc_xinfo.MaxFrameSize = LMC_PKT_BUF_SZ;
+ sc->lmc_xinfo.link_status = sc->lmc_media->get_link_status (sc);
+ sc->lmc_xinfo.mii_reg16 = lmc_mii_readreg (sc, 0, 16);
+
+ sc->lmc_xinfo.Magic1 = 0xDEADBEEF;
+
+ if (copy_to_user(ifr->ifr_data, &sc->lmc_xinfo,
+ sizeof (struct lmc_xinfo)))
+ return -EFAULT;
+ ret = 0;
+
+ break;
+
+ case LMCIOCGETLMCSTATS: /*fold01*/
+ if (sc->lmc_cardtype == LMC_CARDTYPE_T1){
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_FERR_LSB);
+ sc->stats.framingBitErrorCount +=
+ lmc_mii_readreg (sc, 0, 18) & 0xff;
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_FERR_MSB);
+ sc->stats.framingBitErrorCount +=
+ (lmc_mii_readreg (sc, 0, 18) & 0xff) << 8;
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_LCV_LSB);
+ sc->stats.lineCodeViolationCount +=
+ lmc_mii_readreg (sc, 0, 18) & 0xff;
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_LCV_MSB);
+ sc->stats.lineCodeViolationCount +=
+ (lmc_mii_readreg (sc, 0, 18) & 0xff) << 8;
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_AERR);
+ regVal = lmc_mii_readreg (sc, 0, 18) & 0xff;
+
+ sc->stats.lossOfFrameCount +=
+ (regVal & T1FRAMER_LOF_MASK) >> 4;
+ sc->stats.changeOfFrameAlignmentCount +=
+ (regVal & T1FRAMER_COFA_MASK) >> 2;
+ sc->stats.severelyErroredFrameCount +=
+ regVal & T1FRAMER_SEF_MASK;
+ }
+
+ if (copy_to_user(ifr->ifr_data, &sc->stats,
+ sizeof (struct lmc_statistics)))
+ return -EFAULT;
+
+ ret = 0;
+ break;
+
+ case LMCIOCCLEARLMCSTATS: /*fold01*/
+ if (!capable(CAP_NET_ADMIN)){
+ ret = -EPERM;
+ break;
+ }
+
+ memset (&sc->stats, 0, sizeof (struct lmc_statistics));
+ sc->stats.check = STATCHECK;
+ sc->stats.version_size = (DRIVER_VERSION << 16) +
+ sizeof (struct lmc_statistics);
+ sc->stats.lmc_cardtype = sc->lmc_cardtype;
+ ret = 0;
+ break;
+
+ case LMCIOCSETCIRCUIT: /*fold01*/
+ if (!capable(CAP_NET_ADMIN)){
+ ret = -EPERM;
+ break;
+ }
+
+ if(dev->flags & IFF_UP){
+ ret = -EBUSY;
+ break;
+ }
+
+ if (copy_from_user(&ctl, ifr->ifr_data, sizeof (lmc_ctl_t)))
+ return -EFAULT;
+ sc->lmc_media->set_circuit_type(sc, ctl.circuit_type);
+ sc->ictl.circuit_type = ctl.circuit_type;
+ ret = 0;
+
+ break;
+
+ case LMCIOCRESET: /*fold01*/
+ if (!capable(CAP_NET_ADMIN)){
+ ret = -EPERM;
+ break;
+ }
+
+ /* Reset driver and bring back to current state */
+ printk (" REG16 before reset +%04x\n", lmc_mii_readreg (sc, 0, 16));
+ lmc_running_reset (dev);
+ printk (" REG16 after reset +%04x\n", lmc_mii_readreg (sc, 0, 16));
+
+ LMC_EVENT_LOG(LMC_EVENT_FORCEDRESET, LMC_CSR_READ (sc, csr_status), lmc_mii_readreg (sc, 0, 16));
+
+ ret = 0;
+ break;
+
+#ifdef DEBUG
+ case LMCIOCDUMPEVENTLOG:
+ if (copy_to_user(ifr->ifr_data, &lmcEventLogIndex, sizeof (u32)))
+ return -EFAULT;
+ if (copy_to_user(ifr->ifr_data + sizeof (u32), lmcEventLogBuf, sizeof (lmcEventLogBuf)))
+ return -EFAULT;
+
+ ret = 0;
+ break;
+#endif /* end ifdef _DBG_EVENTLOG */
+ case LMCIOCT1CONTROL: /*fold01*/
+ if (sc->lmc_cardtype != LMC_CARDTYPE_T1){
+ ret = -EOPNOTSUPP;
+ break;
+ }
+ break;
+ case LMCIOCXILINX: /*fold01*/
+ {
+ struct lmc_xilinx_control xc; /*fold02*/
+
+ if (!capable(CAP_NET_ADMIN)){
+ ret = -EPERM;
+ break;
+ }
+
+ /*
+ * Stop the xwitter whlie we restart the hardware
+ */
+ netif_stop_queue(dev);
+
+ if (copy_from_user(&xc, ifr->ifr_data, sizeof (struct lmc_xilinx_control)))
+ return -EFAULT;
+ switch(xc.command){
+ case lmc_xilinx_reset: /*fold02*/
+ {
+ u16 mii;
+ mii = lmc_mii_readreg (sc, 0, 16);
+
+ /*
+ * Make all of them 0 and make input
+ */
+ lmc_gpio_mkinput(sc, 0xff);
+
+ /*
+ * make the reset output
+ */
+ lmc_gpio_mkoutput(sc, LMC_GEP_RESET);
+
+ /*
+ * RESET low to force configuration. This also forces
+ * the transmitter clock to be internal, but we expect to reset
+ * that later anyway.
+ */
+
+ sc->lmc_gpio &= ~LMC_GEP_RESET;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+
+ /*
+ * hold for more than 10 microseconds
+ */
+ udelay(50);
+
+ sc->lmc_gpio |= LMC_GEP_RESET;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+
+ /*
+ * stop driving Xilinx-related signals
+ */
+ lmc_gpio_mkinput(sc, 0xff);
+
+ /* Reset the frammer hardware */
+ sc->lmc_media->set_link_status (sc, 1);
+ sc->lmc_media->set_status (sc, NULL);
+// lmc_softreset(sc);
+
+ {
+ int i;
+ for(i = 0; i < 5; i++){
+ lmc_led_on(sc, LMC_DS3_LED0);
+ mdelay(100);
+ lmc_led_off(sc, LMC_DS3_LED0);
+ lmc_led_on(sc, LMC_DS3_LED1);
+ mdelay(100);
+ lmc_led_off(sc, LMC_DS3_LED1);
+ lmc_led_on(sc, LMC_DS3_LED3);
+ mdelay(100);
+ lmc_led_off(sc, LMC_DS3_LED3);
+ lmc_led_on(sc, LMC_DS3_LED2);
+ mdelay(100);
+ lmc_led_off(sc, LMC_DS3_LED2);
+ }
+ }
+
+
+
+ ret = 0x0;
+
+ }
+
+ break;
+ case lmc_xilinx_load_prom: /*fold02*/
+ {
+ u16 mii;
+ int timeout = 500000;
+ mii = lmc_mii_readreg (sc, 0, 16);
+
+ /*
+ * Make all of them 0 and make input
+ */
+ lmc_gpio_mkinput(sc, 0xff);
+
+ /*
+ * make the reset output
+ */
+ lmc_gpio_mkoutput(sc, LMC_GEP_DP | LMC_GEP_RESET);
+
+ /*
+ * RESET low to force configuration. This also forces
+ * the transmitter clock to be internal, but we expect to reset
+ * that later anyway.
+ */
+
+ sc->lmc_gpio &= ~(LMC_GEP_RESET | LMC_GEP_DP);
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+
+ /*
+ * hold for more than 10 microseconds
+ */
+ udelay(50);
+
+ sc->lmc_gpio |= LMC_GEP_DP | LMC_GEP_RESET;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+ /*
+ * busy wait for the chip to reset
+ */
+ while( (LMC_CSR_READ(sc, csr_gp) & LMC_GEP_INIT) == 0 &&
+ (timeout-- > 0))
+ ;
+
+
+ /*
+ * stop driving Xilinx-related signals
+ */
+ lmc_gpio_mkinput(sc, 0xff);
+
+ ret = 0x0;
+
+
+ break;
+
+ }
+
+ case lmc_xilinx_load: /*fold02*/
+ {
+ char *data;
+ int pos;
+ int timeout = 500000;
+
+ if(xc.data == 0x0){
+ ret = -EINVAL;
+ break;
+ }
+
+ data = kmalloc(xc.len, GFP_KERNEL);
+ if(data == 0x0){
+ printk(KERN_WARNING "%s: Failed to allocate memory for copy\n", dev->name);
+ ret = -ENOMEM;
+ break;
+ }
+
+ if(copy_from_user(data, xc.data, xc.len))
+ {
+ kfree(data);
+ ret = -ENOMEM;
+ break;
+ }
+
+ printk("%s: Starting load of data Len: %d at 0x%p == 0x%p\n", dev->name, xc.len, xc.data, data);
+
+ lmc_gpio_mkinput(sc, 0xff);
+
+ /*
+ * Clear the Xilinx and start prgramming from the DEC
+ */
+
+ /*
+ * Set ouput as:
+ * Reset: 0 (active)
+ * DP: 0 (active)
+ * Mode: 1
+ *
+ */
+ sc->lmc_gpio = 0x00;
+ sc->lmc_gpio &= ~LMC_GEP_DP;
+ sc->lmc_gpio &= ~LMC_GEP_RESET;
+ sc->lmc_gpio |= LMC_GEP_MODE;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+ lmc_gpio_mkoutput(sc, LMC_GEP_MODE | LMC_GEP_DP | LMC_GEP_RESET);
+
+ /*
+ * Wait at least 10 us 20 to be safe
+ */
+ udelay(50);
+
+ /*
+ * Clear reset and activate programming lines
+ * Reset: Input
+ * DP: Input
+ * Clock: Output
+ * Data: Output
+ * Mode: Output
+ */
+ lmc_gpio_mkinput(sc, LMC_GEP_DP | LMC_GEP_RESET);
+
+ /*
+ * Set LOAD, DATA, Clock to 1
+ */
+ sc->lmc_gpio = 0x00;
+ sc->lmc_gpio |= LMC_GEP_MODE;
+ sc->lmc_gpio |= LMC_GEP_DATA;
+ sc->lmc_gpio |= LMC_GEP_CLK;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+ lmc_gpio_mkoutput(sc, LMC_GEP_DATA | LMC_GEP_CLK | LMC_GEP_MODE );
+
+ /*
+ * busy wait for the chip to reset
+ */
+ while( (LMC_CSR_READ(sc, csr_gp) & LMC_GEP_INIT) == 0 &&
+ (timeout-- > 0))
+ ;
+
+ printk(KERN_DEBUG "%s: Waited %d for the Xilinx to clear it's memory\n", dev->name, 500000-timeout);
+
+ for(pos = 0; pos < xc.len; pos++){
+ switch(data[pos]){
+ case 0:
+ sc->lmc_gpio &= ~LMC_GEP_DATA; /* Data is 0 */
+ break;
+ case 1:
+ sc->lmc_gpio |= LMC_GEP_DATA; /* Data is 1 */
+ break;
+ default:
+ printk(KERN_WARNING "%s Bad data in xilinx programming data at %d, got %d wanted 0 or 1\n", dev->name, pos, data[pos]);
+ sc->lmc_gpio |= LMC_GEP_DATA; /* Assume it's 1 */
+ }
+ sc->lmc_gpio &= ~LMC_GEP_CLK; /* Clock to zero */
+ sc->lmc_gpio |= LMC_GEP_MODE;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+ udelay(1);
+
+ sc->lmc_gpio |= LMC_GEP_CLK; /* Put the clack back to one */
+ sc->lmc_gpio |= LMC_GEP_MODE;
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+ udelay(1);
+ }
+ if((LMC_CSR_READ(sc, csr_gp) & LMC_GEP_INIT) == 0){
+ printk(KERN_WARNING "%s: Reprogramming FAILED. Needs to be reprogrammed. (corrupted data)\n", dev->name);
+ }
+ else if((LMC_CSR_READ(sc, csr_gp) & LMC_GEP_DP) == 0){
+ printk(KERN_WARNING "%s: Reprogramming FAILED. Needs to be reprogrammed. (done)\n", dev->name);
+ }
+ else {
+ printk(KERN_DEBUG "%s: Done reprogramming Xilinx, %d bits, good luck!\n", dev->name, pos);
+ }
+
+ lmc_gpio_mkinput(sc, 0xff);
+
+ sc->lmc_miireg16 |= LMC_MII16_FIFO_RESET;
+ lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+ sc->lmc_miireg16 &= ~LMC_MII16_FIFO_RESET;
+ lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+ kfree(data);
+
+ ret = 0;
+
+ break;
+ }
+ default: /*fold02*/
+ ret = -EBADE;
+ break;
+ }
+
+ netif_wake_queue(dev);
+ sc->lmc_txfull = 0;
+
+ }
+ break;
+ default: /*fold01*/
+ /* If we don't know what to do, give the protocol a shot. */
+ ret = lmc_proto_ioctl (sc, ifr, cmd);
+ break;
+ }
+
+ spin_unlock_irqrestore(&sc->lmc_lock, flags); /*fold01*/
+
+ lmc_trace(dev, "lmc_ioctl out");
+
+ return ret;
+}
+
+
+/* the watchdog process that cruises around */
+static void lmc_watchdog (unsigned long data) /*fold00*/
+{
+ struct net_device *dev = (struct net_device *) data;
+ lmc_softc_t *sc;
+ int link_status;
+ u_int32_t ticks;
+ unsigned long flags;
+
+ sc = dev->priv;
+
+ lmc_trace(dev, "lmc_watchdog in");
+
+ spin_lock_irqsave(&sc->lmc_lock, flags);
+
+ if(sc->check != 0xBEAFCAFE){
+ printk("LMC: Corrupt net_device stuct, breaking out\n");
+ spin_unlock_irqrestore(&sc->lmc_lock, flags);
+ return;
+ }
+
+
+ /* Make sure the tx jabber and rx watchdog are off,
+ * and the transmit and receive processes are running.
+ */
+
+ LMC_CSR_WRITE (sc, csr_15, 0x00000011);
+ sc->lmc_cmdmode |= TULIP_CMD_TXRUN | TULIP_CMD_RXRUN;
+ LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
+
+ if (sc->lmc_ok == 0)
+ goto kick_timer;
+
+ LMC_EVENT_LOG(LMC_EVENT_WATCHDOG, LMC_CSR_READ (sc, csr_status), lmc_mii_readreg (sc, 0, 16));
+
+ /* --- begin time out check -----------------------------------
+ * check for a transmit interrupt timeout
+ * Has the packet xmt vs xmt serviced threshold been exceeded */
+ if (sc->lmc_taint_tx == sc->lastlmc_taint_tx &&
+ sc->stats.tx_packets > sc->lasttx_packets &&
+ sc->tx_TimeoutInd == 0)
+ {
+
+ /* wait for the watchdog to come around again */
+ sc->tx_TimeoutInd = 1;
+ }
+ else if (sc->lmc_taint_tx == sc->lastlmc_taint_tx &&
+ sc->stats.tx_packets > sc->lasttx_packets &&
+ sc->tx_TimeoutInd)
+ {
+
+ LMC_EVENT_LOG(LMC_EVENT_XMTINTTMO, LMC_CSR_READ (sc, csr_status), 0);
+
+ sc->tx_TimeoutDisplay = 1;
+ sc->stats.tx_TimeoutCnt++;
+
+ /* DEC chip is stuck, hit it with a RESET!!!! */
+ lmc_running_reset (dev);
+
+
+ /* look at receive & transmit process state to make sure they are running */
+ LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+
+ /* look at: DSR - 02 for Reg 16
+ * CTS - 08
+ * DCD - 10
+ * RI - 20
+ * for Reg 17
+ */
+ LMC_EVENT_LOG(LMC_EVENT_RESET2, lmc_mii_readreg (sc, 0, 16), lmc_mii_readreg (sc, 0, 17));
+
+ /* reset the transmit timeout detection flag */
+ sc->tx_TimeoutInd = 0;
+ sc->lastlmc_taint_tx = sc->lmc_taint_tx;
+ sc->lasttx_packets = sc->stats.tx_packets;
+ }
+ else
+ {
+ sc->tx_TimeoutInd = 0;
+ sc->lastlmc_taint_tx = sc->lmc_taint_tx;
+ sc->lasttx_packets = sc->stats.tx_packets;
+ }
+
+ /* --- end time out check ----------------------------------- */
+
+
+ link_status = sc->lmc_media->get_link_status (sc);
+
+ /*
+ * hardware level link lost, but the interface is marked as up.
+ * Mark it as down.
+ */
+ if ((link_status == 0) && (sc->last_link_status != 0)) {
+ printk(KERN_WARNING "%s: hardware/physical link down\n", dev->name);
+ sc->last_link_status = 0;
+ /* lmc_reset (sc); Why reset??? The link can go down ok */
+
+ /* Inform the world that link has been lost */
+ dev->flags &= ~IFF_RUNNING;
+ }
+
+ /*
+ * hardware link is up, but the interface is marked as down.
+ * Bring it back up again.
+ */
+ if (link_status != 0 && sc->last_link_status == 0) {
+ printk(KERN_WARNING "%s: hardware/physical link up\n", dev->name);
+ sc->last_link_status = 1;
+ /* lmc_reset (sc); Again why reset??? */
+
+ /* Inform the world that link protocol is back up. */
+ dev->flags |= IFF_RUNNING;
+
+ /* Now we have to tell the syncppp that we had an outage
+ * and that it should deal. Calling sppp_reopen here
+ * should do the trick, but we may have to call sppp_close
+ * when the link goes down, and call sppp_open here.
+ * Subject to more testing.
+ * --bbraun
+ */
+
+ lmc_proto_reopen(sc);
+
+ }
+
+ /* Call media specific watchdog functions */
+ sc->lmc_media->watchdog(sc);
+
+ /*
+ * Poke the transmitter to make sure it
+ * never stops, even if we run out of mem
+ */
+ LMC_CSR_WRITE(sc, csr_rxpoll, 0);
+
+ /*
+ * Check for code that failed
+ * and try and fix it as appropriate
+ */
+ if(sc->failed_ring == 1){
+ /*
+ * Failed to setup the recv/xmit rin
+ * Try again
+ */
+ sc->failed_ring = 0;
+ lmc_softreset(sc);
+ }
+ if(sc->failed_recv_alloc == 1){
+ /*
+ * We failed to alloc mem in the
+ * interrupt handler, go through the rings
+ * and rebuild them
+ */
+ sc->failed_recv_alloc = 0;
+ lmc_softreset(sc);
+ }
+
+
+ /*
+ * remember the timer value
+ */
+kick_timer:
+
+ ticks = LMC_CSR_READ (sc, csr_gp_timer);
+ LMC_CSR_WRITE (sc, csr_gp_timer, 0xffffffffUL);
+ sc->ictl.ticks = 0x0000ffff - (ticks & 0x0000ffff);
+
+ /*
+ * restart this timer.
+ */
+ sc->timer.expires = jiffies + (HZ);
+ add_timer (&sc->timer);
+
+ spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+ lmc_trace(dev, "lmc_watchdog out");
+
+}
+
+static void lmc_setup(struct net_device * const dev) /*fold00*/
+{
+ lmc_trace(dev, "lmc_setup in");
+
+ dev->type = ARPHRD_HDLC;
+ dev->hard_start_xmit = lmc_start_xmit;
+ dev->open = lmc_open;
+ dev->stop = lmc_close;
+ dev->get_stats = lmc_get_stats;
+ dev->do_ioctl = lmc_ioctl;
+ dev->tx_timeout = lmc_driver_timeout;
+ dev->watchdog_timeo = (HZ); /* 1 second */
+
+ lmc_trace(dev, "lmc_setup out");
+}
+
+
+static int __devinit lmc_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct net_device *dev;
+ lmc_softc_t *sc;
+ u16 subdevice;
+ u_int16_t AdapModelNum;
+ int err = -ENOMEM;
+ static int cards_found;
+#ifndef GCOM
+ /* We name by type not by vendor */
+ static const char lmcname[] = "hdlc%d";
+#else
+ /*
+ * GCOM uses LMC vendor name so that clients can know which card
+ * to attach to.
+ */
+ static const char lmcname[] = "lmc%d";
+#endif
+
+
+ /*
+ * Allocate our own device structure
+ */
+ dev = alloc_netdev(sizeof(lmc_softc_t), lmcname, lmc_setup);
+ if (!dev) {
+ printk (KERN_ERR "lmc:alloc_netdev for device failed\n");
+ goto out1;
+ }
+
+ lmc_trace(dev, "lmc_init_one in");
+
+ err = pci_enable_device(pdev);
+ if (err) {
+ printk(KERN_ERR "lmc: pci enable failed:%d\n", err);
+ goto out2;
+ }
+
+ if (pci_request_regions(pdev, "lmc")) {
+ printk(KERN_ERR "lmc: pci_request_region failed\n");
+ err = -EIO;
+ goto out3;
+ }
+
+ pci_set_drvdata(pdev, dev);
+
+ if(lmc_first_load == 0){
+ printk(KERN_INFO "Lan Media Corporation WAN Driver Version %d.%d.%d\n",
+ DRIVER_MAJOR_VERSION, DRIVER_MINOR_VERSION,DRIVER_SUB_VERSION);
+ lmc_first_load = 1;
+ }
+
+ sc = dev->priv;
+ sc->lmc_device = dev;
+ sc->name = dev->name;
+
+ /* Initialize the sppp layer */
+ /* An ioctl can cause a subsequent detach for raw frame interface */
+ sc->if_type = LMC_PPP;
+ sc->check = 0xBEAFCAFE;
+ dev->base_addr = pci_resource_start(pdev, 0);
+ dev->irq = pdev->irq;
+
+ SET_MODULE_OWNER(dev);
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
+ /*
+ * This will get the protocol layer ready and do any 1 time init's
+ * Must have a valid sc and dev structure
+ */
+ lmc_proto_init(sc);
+
+ lmc_proto_attach(sc);
+
+ /*
+ * Why were we changing this???
+ dev->tx_queue_len = 100;
+ */
+
+ /* Init the spin lock so can call it latter */
+
+ spin_lock_init(&sc->lmc_lock);
+ pci_set_master(pdev);
+
+ printk ("%s: detected at %lx, irq %d\n", dev->name,
+ dev->base_addr, dev->irq);
+
+ if (register_netdev (dev) != 0) {
+ printk (KERN_ERR "%s: register_netdev failed.\n", dev->name);
+ goto out4;
+ }
+
+ sc->lmc_cardtype = LMC_CARDTYPE_UNKNOWN;
+ sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_EXT;
+
+ /*
+ *
+ * Check either the subvendor or the subdevice, some systems reverse
+ * the setting in the bois, seems to be version and arch dependent?
+ * Fix the error, exchange the two values
+ */
+ if ((subdevice = pdev->subsystem_device) == PCI_VENDOR_ID_LMC)
+ subdevice = pdev->subsystem_vendor;
+
+ switch (subdevice) {
+ case PCI_DEVICE_ID_LMC_HSSI:
+ printk ("%s: LMC HSSI\n", dev->name);
+ sc->lmc_cardtype = LMC_CARDTYPE_HSSI;
+ sc->lmc_media = &lmc_hssi_media;
+ break;
+ case PCI_DEVICE_ID_LMC_DS3:
+ printk ("%s: LMC DS3\n", dev->name);
+ sc->lmc_cardtype = LMC_CARDTYPE_DS3;
+ sc->lmc_media = &lmc_ds3_media;
+ break;
+ case PCI_DEVICE_ID_LMC_SSI:
+ printk ("%s: LMC SSI\n", dev->name);
+ sc->lmc_cardtype = LMC_CARDTYPE_SSI;
+ sc->lmc_media = &lmc_ssi_media;
+ break;
+ case PCI_DEVICE_ID_LMC_T1:
+ printk ("%s: LMC T1\n", dev->name);
+ sc->lmc_cardtype = LMC_CARDTYPE_T1;
+ sc->lmc_media = &lmc_t1_media;
+ break;
+ default:
+ printk (KERN_WARNING "%s: LMC UNKOWN CARD!\n", dev->name);
+ break;
+ }
+
+ lmc_initcsrs (sc, dev->base_addr, 8);
+
+ lmc_gpio_mkinput (sc, 0xff);
+ sc->lmc_gpio = 0; /* drive no signals yet */
+
+ sc->lmc_media->defaults (sc);
+
+ sc->lmc_media->set_link_status (sc, LMC_LINK_UP);
+
+ /* verify that the PCI Sub System ID matches the Adapter Model number
+ * from the MII register
+ */
+ AdapModelNum = (lmc_mii_readreg (sc, 0, 3) & 0x3f0) >> 4;
+
+ if ((AdapModelNum == LMC_ADAP_T1
+ && subdevice == PCI_DEVICE_ID_LMC_T1) || /* detect LMC1200 */
+ (AdapModelNum == LMC_ADAP_SSI
+ && subdevice == PCI_DEVICE_ID_LMC_SSI) || /* detect LMC1000 */
+ (AdapModelNum == LMC_ADAP_DS3
+ && subdevice == PCI_DEVICE_ID_LMC_DS3) || /* detect LMC5245 */
+ (AdapModelNum == LMC_ADAP_HSSI
+ && subdevice == PCI_DEVICE_ID_LMC_HSSI))
+ { /* detect LMC5200 */
+
+ }
+ else {
+ printk ("%s: Model number (%d) miscompare for PCI Subsystem ID = 0x%04x\n",
+ dev->name, AdapModelNum, subdevice);
+// return (NULL);
+ }
+ /*
+ * reset clock
+ */
+ LMC_CSR_WRITE (sc, csr_gp_timer, 0xFFFFFFFFUL);
+
+ sc->board_idx = cards_found++;
+ sc->stats.check = STATCHECK;
+ sc->stats.version_size = (DRIVER_VERSION << 16) +
+ sizeof (struct lmc_statistics);
+ sc->stats.lmc_cardtype = sc->lmc_cardtype;
+
+ sc->lmc_ok = 0;
+ sc->last_link_status = 0;
+
+ lmc_trace(dev, "lmc_init_one out");
+ return 0;
+
+ out4:
+ lmc_proto_detach(sc);
+ out3:
+ if (pdev) {
+ pci_release_regions(pdev);
+ pci_set_drvdata(pdev, NULL);
+ }
+ out2:
+ free_netdev(dev);
+ out1:
+ return err;
+}
+
+/*
+ * Called from pci when removing module.
+ */
+static void __devexit lmc_remove_one (struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata(pdev);
+
+ if (dev) {
+ lmc_softc_t *sc = dev->priv;
+
+ printk("%s: removing...\n", dev->name);
+ lmc_proto_detach(sc);
+ unregister_netdev(dev);
+ free_netdev(dev);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ pci_set_drvdata(pdev, NULL);
+ }
+}
+
+/* After this is called, packets can be sent.
+ * Does not initialize the addresses
+ */
+static int lmc_open (struct net_device *dev) /*fold00*/
+{
+ lmc_softc_t *sc = dev->priv;
+
+ lmc_trace(dev, "lmc_open in");
+
+ lmc_led_on(sc, LMC_DS3_LED0);
+
+ lmc_dec_reset (sc);
+ lmc_reset (sc);
+
+ LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+ LMC_EVENT_LOG(LMC_EVENT_RESET2,
+ lmc_mii_readreg (sc, 0, 16),
+ lmc_mii_readreg (sc, 0, 17));
+
+
+ if (sc->lmc_ok){
+ lmc_trace(dev, "lmc_open lmc_ok out");
+ return (0);
+ }
+
+ lmc_softreset (sc);
+
+ /* Since we have to use PCI bus, this should work on x86,alpha,ppc */
+ if (request_irq (dev->irq, &lmc_interrupt, SA_SHIRQ, dev->name, dev)){
+ printk(KERN_WARNING "%s: could not get irq: %d\n", dev->name, dev->irq);
+ lmc_trace(dev, "lmc_open irq failed out");
+ return -EAGAIN;
+ }
+ sc->got_irq = 1;
+
+ /* Assert Terminal Active */
+ sc->lmc_miireg16 |= LMC_MII16_LED_ALL;
+ sc->lmc_media->set_link_status (sc, LMC_LINK_UP);
+
+ /*
+ * reset to last state.
+ */
+ sc->lmc_media->set_status (sc, NULL);
+
+ /* setup default bits to be used in tulip_desc_t transmit descriptor
+ * -baz */
+ sc->TxDescriptControlInit = (
+ LMC_TDES_INTERRUPT_ON_COMPLETION
+ | LMC_TDES_FIRST_SEGMENT
+ | LMC_TDES_LAST_SEGMENT
+ | LMC_TDES_SECOND_ADDR_CHAINED
+ | LMC_TDES_DISABLE_PADDING
+ );
+
+ if (sc->ictl.crc_length == LMC_CTL_CRC_LENGTH_16) {
+ /* disable 32 bit CRC generated by ASIC */
+ sc->TxDescriptControlInit |= LMC_TDES_ADD_CRC_DISABLE;
+ }
+ sc->lmc_media->set_crc_length(sc, sc->ictl.crc_length);
+ /* Acknoledge the Terminal Active and light LEDs */
+
+ /* dev->flags |= IFF_UP; */
+
+ lmc_proto_open(sc);
+
+ dev->do_ioctl = lmc_ioctl;
+
+
+ netif_start_queue(dev);
+
+ sc->stats.tx_tbusy0++ ;
+
+ /*
+ * select what interrupts we want to get
+ */
+ sc->lmc_intrmask = 0;
+ /* Should be using the default interrupt mask defined in the .h file. */
+ sc->lmc_intrmask |= (TULIP_STS_NORMALINTR
+ | TULIP_STS_RXINTR
+ | TULIP_STS_TXINTR
+ | TULIP_STS_ABNRMLINTR
+ | TULIP_STS_SYSERROR
+ | TULIP_STS_TXSTOPPED
+ | TULIP_STS_TXUNDERFLOW
+ | TULIP_STS_RXSTOPPED
+ | TULIP_STS_RXNOBUF
+ );
+ LMC_CSR_WRITE (sc, csr_intr, sc->lmc_intrmask);
+
+ sc->lmc_cmdmode |= TULIP_CMD_TXRUN;
+ sc->lmc_cmdmode |= TULIP_CMD_RXRUN;
+ LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
+
+ sc->lmc_ok = 1; /* Run watchdog */
+
+ /*
+ * Set the if up now - pfb
+ */
+
+ sc->last_link_status = 1;
+
+ /*
+ * Setup a timer for the watchdog on probe, and start it running.
+ * Since lmc_ok == 0, it will be a NOP for now.
+ */
+ init_timer (&sc->timer);
+ sc->timer.expires = jiffies + HZ;
+ sc->timer.data = (unsigned long) dev;
+ sc->timer.function = &lmc_watchdog;
+ add_timer (&sc->timer);
+
+ lmc_trace(dev, "lmc_open out");
+
+ return (0);
+}
+
+/* Total reset to compensate for the AdTran DSU doing bad things
+ * under heavy load
+ */
+
+static void lmc_running_reset (struct net_device *dev) /*fold00*/
+{
+
+ lmc_softc_t *sc = (lmc_softc_t *) dev->priv;
+
+ lmc_trace(dev, "lmc_runnig_reset in");
+
+ /* stop interrupts */
+ /* Clear the interrupt mask */
+ LMC_CSR_WRITE (sc, csr_intr, 0x00000000);
+
+ lmc_dec_reset (sc);
+ lmc_reset (sc);
+ lmc_softreset (sc);
+ /* sc->lmc_miireg16 |= LMC_MII16_LED_ALL; */
+ sc->lmc_media->set_link_status (sc, 1);
+ sc->lmc_media->set_status (sc, NULL);
+
+ //dev->flags |= IFF_RUNNING;
+
+ netif_wake_queue(dev);
+
+ sc->lmc_txfull = 0;
+ sc->stats.tx_tbusy0++ ;
+
+ sc->lmc_intrmask = TULIP_DEFAULT_INTR_MASK;
+ LMC_CSR_WRITE (sc, csr_intr, sc->lmc_intrmask);
+
+ sc->lmc_cmdmode |= (TULIP_CMD_TXRUN | TULIP_CMD_RXRUN);
+ LMC_CSR_WRITE (sc, csr_command, sc->lmc_cmdmode);
+
+ lmc_trace(dev, "lmc_runnin_reset_out");
+}
+
+
+/* This is what is called when you ifconfig down a device.
+ * This disables the timer for the watchdog and keepalives,
+ * and disables the irq for dev.
+ */
+static int lmc_close (struct net_device *dev) /*fold00*/
+{
+ /* not calling release_region() as we should */
+ lmc_softc_t *sc;
+
+ lmc_trace(dev, "lmc_close in");
+
+ sc = dev->priv;
+ sc->lmc_ok = 0;
+ sc->lmc_media->set_link_status (sc, 0);
+ del_timer (&sc->timer);
+ lmc_proto_close(sc);
+ lmc_ifdown (dev);
+
+ lmc_trace(dev, "lmc_close out");
+
+ return 0;
+}
+
+/* Ends the transfer of packets */
+/* When the interface goes down, this is called */
+static int lmc_ifdown (struct net_device *dev) /*fold00*/
+{
+ lmc_softc_t *sc = dev->priv;
+ u32 csr6;
+ int i;
+
+ lmc_trace(dev, "lmc_ifdown in");
+
+ /* Don't let anything else go on right now */
+ // dev->start = 0;
+ netif_stop_queue(dev);
+ sc->stats.tx_tbusy1++ ;
+
+ /* stop interrupts */
+ /* Clear the interrupt mask */
+ LMC_CSR_WRITE (sc, csr_intr, 0x00000000);
+
+ /* Stop Tx and Rx on the chip */
+ csr6 = LMC_CSR_READ (sc, csr_command);
+ csr6 &= ~LMC_DEC_ST; /* Turn off the Transmission bit */
+ csr6 &= ~LMC_DEC_SR; /* Turn off the Receive bit */
+ LMC_CSR_WRITE (sc, csr_command, csr6);
+
+ dev->flags &= ~IFF_RUNNING;
+
+ sc->stats.rx_missed_errors +=
+ LMC_CSR_READ (sc, csr_missed_frames) & 0xffff;
+
+ /* release the interrupt */
+ if(sc->got_irq == 1){
+ free_irq (dev->irq, dev);
+ sc->got_irq = 0;
+ }
+
+ /* free skbuffs in the Rx queue */
+ for (i = 0; i < LMC_RXDESCS; i++)
+ {
+ struct sk_buff *skb = sc->lmc_rxq[i];
+ sc->lmc_rxq[i] = NULL;
+ sc->lmc_rxring[i].status = 0;
+ sc->lmc_rxring[i].length = 0;
+ sc->lmc_rxring[i].buffer1 = 0xDEADBEEF;
+ if (skb != NULL)
+ dev_kfree_skb(skb);
+ sc->lmc_rxq[i] = NULL;
+ }
+
+ for (i = 0; i < LMC_TXDESCS; i++)
+ {
+ if (sc->lmc_txq[i] != NULL)
+ dev_kfree_skb(sc->lmc_txq[i]);
+ sc->lmc_txq[i] = NULL;
+ }
+
+ lmc_led_off (sc, LMC_MII16_LED_ALL);
+
+ netif_wake_queue(dev);
+ sc->stats.tx_tbusy0++ ;
+
+ lmc_trace(dev, "lmc_ifdown out");
+
+ return 0;
+}
+
+/* Interrupt handling routine. This will take an incoming packet, or clean
+ * up after a trasmit.
+ */
+static irqreturn_t lmc_interrupt (int irq, void *dev_instance, struct pt_regs *regs) /*fold00*/
+{
+ struct net_device *dev = (struct net_device *) dev_instance;
+ lmc_softc_t *sc;
+ u32 csr;
+ int i;
+ s32 stat;
+ unsigned int badtx;
+ u32 firstcsr;
+ int max_work = LMC_RXDESCS;
+ int handled = 0;
+
+ lmc_trace(dev, "lmc_interrupt in");
+
+ sc = dev->priv;
+
+ spin_lock(&sc->lmc_lock);
+
+ /*
+ * Read the csr to find what interrupts we have (if any)
+ */
+ csr = LMC_CSR_READ (sc, csr_status);
+
+ /*
+ * Make sure this is our interrupt
+ */
+ if ( ! (csr & sc->lmc_intrmask)) {
+ goto lmc_int_fail_out;
+ }
+
+ firstcsr = csr;
+
+ /* always go through this loop at least once */
+ while (csr & sc->lmc_intrmask) {
+ handled = 1;
+
+ /*
+ * Clear interrupt bits, we handle all case below
+ */
+ LMC_CSR_WRITE (sc, csr_status, csr);
+
+ /*
+ * One of
+ * - Transmit process timed out CSR5<1>
+ * - Transmit jabber timeout CSR5<3>
+ * - Transmit underflow CSR5<5>
+ * - Transmit Receiver buffer unavailable CSR5<7>
+ * - Receive process stopped CSR5<8>
+ * - Receive watchdog timeout CSR5<9>
+ * - Early transmit interrupt CSR5<10>
+ *
+ * Is this really right? Should we do a running reset for jabber?
+ * (being a WAN card and all)
+ */
+ if (csr & TULIP_STS_ABNRMLINTR){
+ lmc_running_reset (dev);
+ break;
+ }
+
+ if (csr & TULIP_STS_RXINTR){
+ lmc_trace(dev, "rx interrupt");
+ lmc_rx (dev);
+
+ }
+ if (csr & (TULIP_STS_TXINTR | TULIP_STS_TXNOBUF | TULIP_STS_TXSTOPPED)) {
+
+ int n_compl = 0 ;
+ /* reset the transmit timeout detection flag -baz */
+ sc->stats.tx_NoCompleteCnt = 0;
+
+ badtx = sc->lmc_taint_tx;
+ i = badtx % LMC_TXDESCS;
+
+ while ((badtx < sc->lmc_next_tx)) {
+ stat = sc->lmc_txring[i].status;
+
+ LMC_EVENT_LOG (LMC_EVENT_XMTINT, stat,
+ sc->lmc_txring[i].length);
+ /*
+ * If bit 31 is 1 the tulip owns it break out of the loop
+ */
+ if (stat & 0x80000000)
+ break;
+
+ n_compl++ ; /* i.e., have an empty slot in ring */
+ /*
+ * If we have no skbuff or have cleared it
+ * Already continue to the next buffer
+ */
+ if (sc->lmc_txq[i] == NULL)
+ continue;
+
+ /*
+ * Check the total error summary to look for any errors
+ */
+ if (stat & 0x8000) {
+ sc->stats.tx_errors++;
+ if (stat & 0x4104)
+ sc->stats.tx_aborted_errors++;
+ if (stat & 0x0C00)
+ sc->stats.tx_carrier_errors++;
+ if (stat & 0x0200)
+ sc->stats.tx_window_errors++;
+ if (stat & 0x0002)
+ sc->stats.tx_fifo_errors++;
+ }
+ else {
+
+ sc->stats.tx_bytes += sc->lmc_txring[i].length & 0x7ff;
+
+ sc->stats.tx_packets++;
+ }
+
+ // dev_kfree_skb(sc->lmc_txq[i]);
+ dev_kfree_skb_irq(sc->lmc_txq[i]);
+ sc->lmc_txq[i] = NULL;
+
+ badtx++;
+ i = badtx % LMC_TXDESCS;
+ }
+
+ if (sc->lmc_next_tx - badtx > LMC_TXDESCS)
+ {
+ printk ("%s: out of sync pointer\n", dev->name);
+ badtx += LMC_TXDESCS;
+ }
+ LMC_EVENT_LOG(LMC_EVENT_TBUSY0, n_compl, 0);
+ sc->lmc_txfull = 0;
+ netif_wake_queue(dev);
+ sc->stats.tx_tbusy0++ ;
+
+
+#ifdef DEBUG
+ sc->stats.dirtyTx = badtx;
+ sc->stats.lmc_next_tx = sc->lmc_next_tx;
+ sc->stats.lmc_txfull = sc->lmc_txfull;
+#endif
+ sc->lmc_taint_tx = badtx;
+
+ /*
+ * Why was there a break here???
+ */
+ } /* end handle transmit interrupt */
+
+ if (csr & TULIP_STS_SYSERROR) {
+ u32 error;
+ printk (KERN_WARNING "%s: system bus error csr: %#8.8x\n", dev->name, csr);
+ error = csr>>23 & 0x7;
+ switch(error){
+ case 0x000:
+ printk(KERN_WARNING "%s: Parity Fault (bad)\n", dev->name);
+ break;
+ case 0x001:
+ printk(KERN_WARNING "%s: Master Abort (naughty)\n", dev->name);
+ break;
+ case 0x010:
+ printk(KERN_WARNING "%s: Target Abort (not so naughty)\n", dev->name);
+ break;
+ default:
+ printk(KERN_WARNING "%s: This bus error code was supposed to be reserved!\n", dev->name);
+ }
+ lmc_dec_reset (sc);
+ lmc_reset (sc);
+ LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+ LMC_EVENT_LOG(LMC_EVENT_RESET2,
+ lmc_mii_readreg (sc, 0, 16),
+ lmc_mii_readreg (sc, 0, 17));
+
+ }
+
+
+ if(max_work-- <= 0)
+ break;
+
+ /*
+ * Get current csr status to make sure
+ * we've cleared all interrupts
+ */
+ csr = LMC_CSR_READ (sc, csr_status);
+ } /* end interrupt loop */
+ LMC_EVENT_LOG(LMC_EVENT_INT, firstcsr, csr);
+
+lmc_int_fail_out:
+
+ spin_unlock(&sc->lmc_lock);
+
+ lmc_trace(dev, "lmc_interrupt out");
+ return IRQ_RETVAL(handled);
+}
+
+static int lmc_start_xmit (struct sk_buff *skb, struct net_device *dev) /*fold00*/
+{
+ lmc_softc_t *sc;
+ u32 flag;
+ int entry;
+ int ret = 0;
+ unsigned long flags;
+
+ lmc_trace(dev, "lmc_start_xmit in");
+
+ sc = dev->priv;
+
+ spin_lock_irqsave(&sc->lmc_lock, flags);
+
+ /* normal path, tbusy known to be zero */
+
+ entry = sc->lmc_next_tx % LMC_TXDESCS;
+
+ sc->lmc_txq[entry] = skb;
+ sc->lmc_txring[entry].buffer1 = virt_to_bus (skb->data);
+
+ LMC_CONSOLE_LOG("xmit", skb->data, skb->len);
+
+#ifndef GCOM
+ /* If the queue is less than half full, don't interrupt */
+ if (sc->lmc_next_tx - sc->lmc_taint_tx < LMC_TXDESCS / 2)
+ {
+ /* Do not interrupt on completion of this packet */
+ flag = 0x60000000;
+ netif_wake_queue(dev);
+ }
+ else if (sc->lmc_next_tx - sc->lmc_taint_tx == LMC_TXDESCS / 2)
+ {
+ /* This generates an interrupt on completion of this packet */
+ flag = 0xe0000000;
+ netif_wake_queue(dev);
+ }
+ else if (sc->lmc_next_tx - sc->lmc_taint_tx < LMC_TXDESCS - 1)
+ {
+ /* Do not interrupt on completion of this packet */
+ flag = 0x60000000;
+ netif_wake_queue(dev);
+ }
+ else
+ {
+ /* This generates an interrupt on completion of this packet */
+ flag = 0xe0000000;
+ sc->lmc_txfull = 1;
+ netif_stop_queue(dev);
+ }
+#else
+ flag = LMC_TDES_INTERRUPT_ON_COMPLETION;
+
+ if (sc->lmc_next_tx - sc->lmc_taint_tx >= LMC_TXDESCS - 1)
+ { /* ring full, go busy */
+ sc->lmc_txfull = 1;
+ netif_stop_queue(dev);
+ sc->stats.tx_tbusy1++ ;
+ LMC_EVENT_LOG(LMC_EVENT_TBUSY1, entry, 0);
+ }
+#endif
+
+
+ if (entry == LMC_TXDESCS - 1) /* last descriptor in ring */
+ flag |= LMC_TDES_END_OF_RING; /* flag as such for Tulip */
+
+ /* don't pad small packets either */
+ flag = sc->lmc_txring[entry].length = (skb->len) | flag |
+ sc->TxDescriptControlInit;
+
+ /* set the transmit timeout flag to be checked in
+ * the watchdog timer handler. -baz
+ */
+
+ sc->stats.tx_NoCompleteCnt++;
+ sc->lmc_next_tx++;
+
+ /* give ownership to the chip */
+ LMC_EVENT_LOG(LMC_EVENT_XMT, flag, entry);
+ sc->lmc_txring[entry].status = 0x80000000;
+
+ /* send now! */
+ LMC_CSR_WRITE (sc, csr_txpoll, 0);
+
+ dev->trans_start = jiffies;
+
+ spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+ lmc_trace(dev, "lmc_start_xmit_out");
+ return ret;
+}
+
+
+static int lmc_rx (struct net_device *dev) /*fold00*/
+{
+ lmc_softc_t *sc;
+ int i;
+ int rx_work_limit = LMC_RXDESCS;
+ unsigned int next_rx;
+ int rxIntLoopCnt; /* debug -baz */
+ int localLengthErrCnt = 0;
+ long stat;
+ struct sk_buff *skb, *nsb;
+ u16 len;
+
+ lmc_trace(dev, "lmc_rx in");
+
+ sc = dev->priv;
+
+ lmc_led_on(sc, LMC_DS3_LED3);
+
+ rxIntLoopCnt = 0; /* debug -baz */
+
+ i = sc->lmc_next_rx % LMC_RXDESCS;
+ next_rx = sc->lmc_next_rx;
+
+ while (((stat = sc->lmc_rxring[i].status) & LMC_RDES_OWN_BIT) != DESC_OWNED_BY_DC21X4)
+ {
+ rxIntLoopCnt++; /* debug -baz */
+ len = ((stat & LMC_RDES_FRAME_LENGTH) >> RDES_FRAME_LENGTH_BIT_NUMBER);
+ if ((stat & 0x0300) != 0x0300) { /* Check first segment and last segment */
+ if ((stat & 0x0000ffff) != 0x7fff) {
+ /* Oversized frame */
+ sc->stats.rx_length_errors++;
+ goto skip_packet;
+ }
+ }
+
+ if(stat & 0x00000008){ /* Catch a dribbling bit error */
+ sc->stats.rx_errors++;
+ sc->stats.rx_frame_errors++;
+ goto skip_packet;
+ }
+
+
+ if(stat & 0x00000004){ /* Catch a CRC error by the Xilinx */
+ sc->stats.rx_errors++;
+ sc->stats.rx_crc_errors++;
+ goto skip_packet;
+ }
+
+
+ if (len > LMC_PKT_BUF_SZ){
+ sc->stats.rx_length_errors++;
+ localLengthErrCnt++;
+ goto skip_packet;
+ }
+
+ if (len < sc->lmc_crcSize + 2) {
+ sc->stats.rx_length_errors++;
+ sc->stats.rx_SmallPktCnt++;
+ localLengthErrCnt++;
+ goto skip_packet;
+ }
+
+ if(stat & 0x00004000){
+ printk(KERN_WARNING "%s: Receiver descriptor error, receiver out of sync?\n", dev->name);
+ }
+
+ len -= sc->lmc_crcSize;
+
+ skb = sc->lmc_rxq[i];
+
+ /*
+ * We ran out of memory at some point
+ * just allocate an skb buff and continue.
+ */
+
+ if(skb == 0x0){
+ nsb = dev_alloc_skb (LMC_PKT_BUF_SZ + 2);
+ if (nsb) {
+ sc->lmc_rxq[i] = nsb;
+ nsb->dev = dev;
+ sc->lmc_rxring[i].buffer1 = virt_to_bus (nsb->tail);
+ }
+ sc->failed_recv_alloc = 1;
+ goto skip_packet;
+ }
+
+ dev->last_rx = jiffies;
+ sc->stats.rx_packets++;
+ sc->stats.rx_bytes += len;
+
+ LMC_CONSOLE_LOG("recv", skb->data, len);
+
+ /*
+ * I'm not sure of the sanity of this
+ * Packets could be arriving at a constant
+ * 44.210mbits/sec and we're going to copy
+ * them into a new buffer??
+ */
+
+ if(len > (LMC_MTU - (LMC_MTU>>2))){ /* len > LMC_MTU * 0.75 */
+ /*
+ * If it's a large packet don't copy it just hand it up
+ */
+ give_it_anyways:
+
+ sc->lmc_rxq[i] = NULL;
+ sc->lmc_rxring[i].buffer1 = 0x0;
+
+ skb_put (skb, len);
+ skb->protocol = lmc_proto_type(sc, skb);
+ skb->protocol = htons(ETH_P_WAN_PPP);
+ skb->mac.raw = skb->data;
+// skb->nh.raw = skb->data;
+ skb->dev = dev;
+ lmc_proto_netif(sc, skb);
+
+ /*
+ * This skb will be destroyed by the upper layers, make a new one
+ */
+ nsb = dev_alloc_skb (LMC_PKT_BUF_SZ + 2);
+ if (nsb) {
+ sc->lmc_rxq[i] = nsb;
+ nsb->dev = dev;
+ sc->lmc_rxring[i].buffer1 = virt_to_bus (nsb->tail);
+ /* Transferred to 21140 below */
+ }
+ else {
+ /*
+ * We've run out of memory, stop trying to allocate
+ * memory and exit the interrupt handler
+ *
+ * The chip may run out of receivers and stop
+ * in which care we'll try to allocate the buffer
+ * again. (once a second)
+ */
+ sc->stats.rx_BuffAllocErr++;
+ LMC_EVENT_LOG(LMC_EVENT_RCVINT, stat, len);
+ sc->failed_recv_alloc = 1;
+ goto skip_out_of_mem;
+ }
+ }
+ else {
+ nsb = dev_alloc_skb(len);
+ if(!nsb) {
+ goto give_it_anyways;
+ }
+ memcpy(skb_put(nsb, len), skb->data, len);
+
+ nsb->protocol = lmc_proto_type(sc, skb);
+ nsb->mac.raw = nsb->data;
+// nsb->nh.raw = nsb->data;
+ nsb->dev = dev;
+ lmc_proto_netif(sc, nsb);
+ }
+
+ skip_packet:
+ LMC_EVENT_LOG(LMC_EVENT_RCVINT, stat, len);
+ sc->lmc_rxring[i].status = DESC_OWNED_BY_DC21X4;
+
+ sc->lmc_next_rx++;
+ i = sc->lmc_next_rx % LMC_RXDESCS;
+ rx_work_limit--;
+ if (rx_work_limit < 0)
+ break;
+ }
+
+ /* detect condition for LMC1000 where DSU cable attaches and fills
+ * descriptors with bogus packets
+ *
+ if (localLengthErrCnt > LMC_RXDESCS - 3) {
+ sc->stats.rx_BadPktSurgeCnt++;
+ LMC_EVENT_LOG(LMC_EVENT_BADPKTSURGE,
+ localLengthErrCnt,
+ sc->stats.rx_BadPktSurgeCnt);
+ } */
+
+ /* save max count of receive descriptors serviced */
+ if (rxIntLoopCnt > sc->stats.rxIntLoopCnt) {
+ sc->stats.rxIntLoopCnt = rxIntLoopCnt; /* debug -baz */
+ }
+
+#ifdef DEBUG
+ if (rxIntLoopCnt == 0)
+ {
+ for (i = 0; i < LMC_RXDESCS; i++)
+ {
+ if ((sc->lmc_rxring[i].status & LMC_RDES_OWN_BIT)
+ != DESC_OWNED_BY_DC21X4)
+ {
+ rxIntLoopCnt++;
+ }
+ }
+ LMC_EVENT_LOG(LMC_EVENT_RCVEND, rxIntLoopCnt, 0);
+ }
+#endif
+
+
+ lmc_led_off(sc, LMC_DS3_LED3);
+
+skip_out_of_mem:
+
+ lmc_trace(dev, "lmc_rx out");
+
+ return 0;
+}
+
+static struct net_device_stats *lmc_get_stats (struct net_device *dev) /*fold00*/
+{
+ lmc_softc_t *sc = dev->priv;
+ unsigned long flags;
+
+ lmc_trace(dev, "lmc_get_stats in");
+
+
+ spin_lock_irqsave(&sc->lmc_lock, flags);
+
+ sc->stats.rx_missed_errors += LMC_CSR_READ (sc, csr_missed_frames) & 0xffff;
+
+ spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+ lmc_trace(dev, "lmc_get_stats out");
+
+ return (struct net_device_stats *) &sc->stats;
+}
+
+static struct pci_driver lmc_driver = {
+ .name = "lmc",
+ .id_table = lmc_pci_tbl,
+ .probe = lmc_init_one,
+ .remove = __devexit_p(lmc_remove_one),
+};
+
+static int __init init_lmc(void)
+{
+ return pci_module_init(&lmc_driver);
+}
+
+static void __exit exit_lmc(void)
+{
+ pci_unregister_driver(&lmc_driver);
+}
+
+module_init(init_lmc);
+module_exit(exit_lmc);
+
+unsigned lmc_mii_readreg (lmc_softc_t * const sc, unsigned devaddr, unsigned regno) /*fold00*/
+{
+ int i;
+ int command = (0xf6 << 10) | (devaddr << 5) | regno;
+ int retval = 0;
+
+ lmc_trace(sc->lmc_device, "lmc_mii_readreg in");
+
+ LMC_MII_SYNC (sc);
+
+ lmc_trace(sc->lmc_device, "lmc_mii_readreg: done sync");
+
+ for (i = 15; i >= 0; i--)
+ {
+ int dataval = (command & (1 << i)) ? 0x20000 : 0;
+
+ LMC_CSR_WRITE (sc, csr_9, dataval);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ LMC_CSR_WRITE (sc, csr_9, dataval | 0x10000);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ }
+
+ lmc_trace(sc->lmc_device, "lmc_mii_readreg: done1");
+
+ for (i = 19; i > 0; i--)
+ {
+ LMC_CSR_WRITE (sc, csr_9, 0x40000);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ retval = (retval << 1) | ((LMC_CSR_READ (sc, csr_9) & 0x80000) ? 1 : 0);
+ LMC_CSR_WRITE (sc, csr_9, 0x40000 | 0x10000);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ }
+
+ lmc_trace(sc->lmc_device, "lmc_mii_readreg out");
+
+ return (retval >> 1) & 0xffff;
+}
+
+void lmc_mii_writereg (lmc_softc_t * const sc, unsigned devaddr, unsigned regno, unsigned data) /*fold00*/
+{
+ int i = 32;
+ int command = (0x5002 << 16) | (devaddr << 23) | (regno << 18) | data;
+
+ lmc_trace(sc->lmc_device, "lmc_mii_writereg in");
+
+ LMC_MII_SYNC (sc);
+
+ i = 31;
+ while (i >= 0)
+ {
+ int datav;
+
+ if (command & (1 << i))
+ datav = 0x20000;
+ else
+ datav = 0x00000;
+
+ LMC_CSR_WRITE (sc, csr_9, datav);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ LMC_CSR_WRITE (sc, csr_9, (datav | 0x10000));
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ i--;
+ }
+
+ i = 2;
+ while (i > 0)
+ {
+ LMC_CSR_WRITE (sc, csr_9, 0x40000);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ LMC_CSR_WRITE (sc, csr_9, 0x50000);
+ lmc_delay ();
+ /* __SLOW_DOWN_IO; */
+ i--;
+ }
+
+ lmc_trace(sc->lmc_device, "lmc_mii_writereg out");
+}
+
+static void lmc_softreset (lmc_softc_t * const sc) /*fold00*/
+{
+ int i;
+
+ lmc_trace(sc->lmc_device, "lmc_softreset in");
+
+ /* Initialize the receive rings and buffers. */
+ sc->lmc_txfull = 0;
+ sc->lmc_next_rx = 0;
+ sc->lmc_next_tx = 0;
+ sc->lmc_taint_rx = 0;
+ sc->lmc_taint_tx = 0;
+
+ /*
+ * Setup each one of the receiver buffers
+ * allocate an skbuff for each one, setup the descriptor table
+ * and point each buffer at the next one
+ */
+
+ for (i = 0; i < LMC_RXDESCS; i++)
+ {
+ struct sk_buff *skb;
+
+ if (sc->lmc_rxq[i] == NULL)
+ {
+ skb = dev_alloc_skb (LMC_PKT_BUF_SZ + 2);
+ if(skb == NULL){
+ printk(KERN_WARNING "%s: Failed to allocate receiver ring, will try again\n", sc->name);
+ sc->failed_ring = 1;
+ break;
+ }
+ else{
+ sc->lmc_rxq[i] = skb;
+ }
+ }
+ else
+ {
+ skb = sc->lmc_rxq[i];
+ }
+
+ skb->dev = sc->lmc_device;
+
+ /* owned by 21140 */
+ sc->lmc_rxring[i].status = 0x80000000;
+
+ /* used to be PKT_BUF_SZ now uses skb since we lose some to head room */
+ sc->lmc_rxring[i].length = skb->end - skb->data;
+
+ /* use to be tail which is dumb since you're thinking why write
+ * to the end of the packj,et but since there's nothing there tail == data
+ */
+ sc->lmc_rxring[i].buffer1 = virt_to_bus (skb->data);
+
+ /* This is fair since the structure is static and we have the next address */
+ sc->lmc_rxring[i].buffer2 = virt_to_bus (&sc->lmc_rxring[i + 1]);
+
+ }
+
+ /*
+ * Sets end of ring
+ */
+ sc->lmc_rxring[i - 1].length |= 0x02000000; /* Set end of buffers flag */
+ sc->lmc_rxring[i - 1].buffer2 = virt_to_bus (&sc->lmc_rxring[0]); /* Point back to the start */
+ LMC_CSR_WRITE (sc, csr_rxlist, virt_to_bus (sc->lmc_rxring)); /* write base address */
+
+
+ /* Initialize the transmit rings and buffers */
+ for (i = 0; i < LMC_TXDESCS; i++)
+ {
+ if (sc->lmc_txq[i] != NULL){ /* have buffer */
+ dev_kfree_skb(sc->lmc_txq[i]); /* free it */
+ sc->stats.tx_dropped++; /* We just dropped a packet */
+ }
+ sc->lmc_txq[i] = NULL;
+ sc->lmc_txring[i].status = 0x00000000;
+ sc->lmc_txring[i].buffer2 = virt_to_bus (&sc->lmc_txring[i + 1]);
+ }
+ sc->lmc_txring[i - 1].buffer2 = virt_to_bus (&sc->lmc_txring[0]);
+ LMC_CSR_WRITE (sc, csr_txlist, virt_to_bus (sc->lmc_txring));
+
+ lmc_trace(sc->lmc_device, "lmc_softreset out");
+}
+
+void lmc_gpio_mkinput(lmc_softc_t * const sc, u_int32_t bits) /*fold00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_gpio_mkinput in");
+ sc->lmc_gpio_io &= ~bits;
+ LMC_CSR_WRITE(sc, csr_gp, TULIP_GP_PINSET | (sc->lmc_gpio_io));
+ lmc_trace(sc->lmc_device, "lmc_gpio_mkinput out");
+}
+
+void lmc_gpio_mkoutput(lmc_softc_t * const sc, u_int32_t bits) /*fold00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_gpio_mkoutput in");
+ sc->lmc_gpio_io |= bits;
+ LMC_CSR_WRITE(sc, csr_gp, TULIP_GP_PINSET | (sc->lmc_gpio_io));
+ lmc_trace(sc->lmc_device, "lmc_gpio_mkoutput out");
+}
+
+void lmc_led_on(lmc_softc_t * const sc, u_int32_t led) /*fold00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_led_on in");
+ if((~sc->lmc_miireg16) & led){ /* Already on! */
+ lmc_trace(sc->lmc_device, "lmc_led_on aon out");
+ return;
+ }
+
+ sc->lmc_miireg16 &= ~led;
+ lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+ lmc_trace(sc->lmc_device, "lmc_led_on out");
+}
+
+void lmc_led_off(lmc_softc_t * const sc, u_int32_t led) /*fold00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_led_off in");
+ if(sc->lmc_miireg16 & led){ /* Already set don't do anything */
+ lmc_trace(sc->lmc_device, "lmc_led_off aoff out");
+ return;
+ }
+
+ sc->lmc_miireg16 |= led;
+ lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+ lmc_trace(sc->lmc_device, "lmc_led_off out");
+}
+
+static void lmc_reset(lmc_softc_t * const sc) /*fold00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_reset in");
+ sc->lmc_miireg16 |= LMC_MII16_FIFO_RESET;
+ lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+ sc->lmc_miireg16 &= ~LMC_MII16_FIFO_RESET;
+ lmc_mii_writereg(sc, 0, 16, sc->lmc_miireg16);
+
+ /*
+ * make some of the GPIO pins be outputs
+ */
+ lmc_gpio_mkoutput(sc, LMC_GEP_RESET);
+
+ /*
+ * RESET low to force state reset. This also forces
+ * the transmitter clock to be internal, but we expect to reset
+ * that later anyway.
+ */
+ sc->lmc_gpio &= ~(LMC_GEP_RESET);
+ LMC_CSR_WRITE(sc, csr_gp, sc->lmc_gpio);
+
+ /*
+ * hold for more than 10 microseconds
+ */
+ udelay(50);
+
+ /*
+ * stop driving Xilinx-related signals
+ */
+ lmc_gpio_mkinput(sc, LMC_GEP_RESET);
+
+ /*
+ * Call media specific init routine
+ */
+ sc->lmc_media->init(sc);
+
+ sc->stats.resetCount++;
+ lmc_trace(sc->lmc_device, "lmc_reset out");
+}
+
+static void lmc_dec_reset(lmc_softc_t * const sc) /*fold00*/
+{
+ u_int32_t val;
+ lmc_trace(sc->lmc_device, "lmc_dec_reset in");
+
+ /*
+ * disable all interrupts
+ */
+ sc->lmc_intrmask = 0;
+ LMC_CSR_WRITE(sc, csr_intr, sc->lmc_intrmask);
+
+ /*
+ * Reset the chip with a software reset command.
+ * Wait 10 microseconds (actually 50 PCI cycles but at
+ * 33MHz that comes to two microseconds but wait a
+ * bit longer anyways)
+ */
+ LMC_CSR_WRITE(sc, csr_busmode, TULIP_BUSMODE_SWRESET);
+ udelay(25);
+#ifdef __sparc__
+ sc->lmc_busmode = LMC_CSR_READ(sc, csr_busmode);
+ sc->lmc_busmode = 0x00100000;
+ sc->lmc_busmode &= ~TULIP_BUSMODE_SWRESET;
+ LMC_CSR_WRITE(sc, csr_busmode, sc->lmc_busmode);
+#endif
+ sc->lmc_cmdmode = LMC_CSR_READ(sc, csr_command);
+
+ /*
+ * We want:
+ * no ethernet address in frames we write
+ * disable padding (txdesc, padding disable)
+ * ignore runt frames (rdes0 bit 15)
+ * no receiver watchdog or transmitter jabber timer
+ * (csr15 bit 0,14 == 1)
+ * if using 16-bit CRC, turn off CRC (trans desc, crc disable)
+ */
+
+ sc->lmc_cmdmode |= ( TULIP_CMD_PROMISCUOUS
+ | TULIP_CMD_FULLDUPLEX
+ | TULIP_CMD_PASSBADPKT
+ | TULIP_CMD_NOHEARTBEAT
+ | TULIP_CMD_PORTSELECT
+ | TULIP_CMD_RECEIVEALL
+ | TULIP_CMD_MUSTBEONE
+ );
+ sc->lmc_cmdmode &= ~( TULIP_CMD_OPERMODE
+ | TULIP_CMD_THRESHOLDCTL
+ | TULIP_CMD_STOREFWD
+ | TULIP_CMD_TXTHRSHLDCTL
+ );
+
+ LMC_CSR_WRITE(sc, csr_command, sc->lmc_cmdmode);
+
+ /*
+ * disable receiver watchdog and transmit jabber
+ */
+ val = LMC_CSR_READ(sc, csr_sia_general);
+ val |= (TULIP_WATCHDOG_TXDISABLE | TULIP_WATCHDOG_RXDISABLE);
+ LMC_CSR_WRITE(sc, csr_sia_general, val);
+
+ lmc_trace(sc->lmc_device, "lmc_dec_reset out");
+}
+
+static void lmc_initcsrs(lmc_softc_t * const sc, lmc_csrptr_t csr_base, /*fold00*/
+ size_t csr_size)
+{
+ lmc_trace(sc->lmc_device, "lmc_initcsrs in");
+ sc->lmc_csrs.csr_busmode = csr_base + 0 * csr_size;
+ sc->lmc_csrs.csr_txpoll = csr_base + 1 * csr_size;
+ sc->lmc_csrs.csr_rxpoll = csr_base + 2 * csr_size;
+ sc->lmc_csrs.csr_rxlist = csr_base + 3 * csr_size;
+ sc->lmc_csrs.csr_txlist = csr_base + 4 * csr_size;
+ sc->lmc_csrs.csr_status = csr_base + 5 * csr_size;
+ sc->lmc_csrs.csr_command = csr_base + 6 * csr_size;
+ sc->lmc_csrs.csr_intr = csr_base + 7 * csr_size;
+ sc->lmc_csrs.csr_missed_frames = csr_base + 8 * csr_size;
+ sc->lmc_csrs.csr_9 = csr_base + 9 * csr_size;
+ sc->lmc_csrs.csr_10 = csr_base + 10 * csr_size;
+ sc->lmc_csrs.csr_11 = csr_base + 11 * csr_size;
+ sc->lmc_csrs.csr_12 = csr_base + 12 * csr_size;
+ sc->lmc_csrs.csr_13 = csr_base + 13 * csr_size;
+ sc->lmc_csrs.csr_14 = csr_base + 14 * csr_size;
+ sc->lmc_csrs.csr_15 = csr_base + 15 * csr_size;
+ lmc_trace(sc->lmc_device, "lmc_initcsrs out");
+}
+
+static void lmc_driver_timeout(struct net_device *dev) { /*fold00*/
+ lmc_softc_t *sc;
+ u32 csr6;
+ unsigned long flags;
+
+ lmc_trace(dev, "lmc_driver_timeout in");
+
+ sc = dev->priv;
+
+ spin_lock_irqsave(&sc->lmc_lock, flags);
+
+ printk("%s: Xmitter busy|\n", dev->name);
+
+ sc->stats.tx_tbusy_calls++ ;
+ if (jiffies - dev->trans_start < TX_TIMEOUT) {
+ goto bug_out;
+ }
+
+ /*
+ * Chip seems to have locked up
+ * Reset it
+ * This whips out all our decriptor
+ * table and starts from scartch
+ */
+
+ LMC_EVENT_LOG(LMC_EVENT_XMTPRCTMO,
+ LMC_CSR_READ (sc, csr_status),
+ sc->stats.tx_ProcTimeout);
+
+ lmc_running_reset (dev);
+
+ LMC_EVENT_LOG(LMC_EVENT_RESET1, LMC_CSR_READ (sc, csr_status), 0);
+ LMC_EVENT_LOG(LMC_EVENT_RESET2,
+ lmc_mii_readreg (sc, 0, 16),
+ lmc_mii_readreg (sc, 0, 17));
+
+ /* restart the tx processes */
+ csr6 = LMC_CSR_READ (sc, csr_command);
+ LMC_CSR_WRITE (sc, csr_command, csr6 | 0x0002);
+ LMC_CSR_WRITE (sc, csr_command, csr6 | 0x2002);
+
+ /* immediate transmit */
+ LMC_CSR_WRITE (sc, csr_txpoll, 0);
+
+ sc->stats.tx_errors++;
+ sc->stats.tx_ProcTimeout++; /* -baz */
+
+ dev->trans_start = jiffies;
+
+bug_out:
+
+ spin_unlock_irqrestore(&sc->lmc_lock, flags);
+
+ lmc_trace(dev, "lmc_driver_timout out");
+
+
+}
diff --git a/drivers/net/wan/lmc/lmc_media.c b/drivers/net/wan/lmc/lmc_media.c
new file mode 100644
index 00000000000..f55ce76b00e
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_media.c
@@ -0,0 +1,1246 @@
+/* $Id: lmc_media.c,v 1.13 2000/04/11 05:25:26 asj Exp $ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inet.h>
+#include <linux/bitops.h>
+
+#include <net/syncppp.h>
+
+#include <asm/processor.h> /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#include <asm/uaccess.h>
+
+#include "lmc.h"
+#include "lmc_var.h"
+#include "lmc_ioctl.h"
+#include "lmc_debug.h"
+
+#define CONFIG_LMC_IGNORE_HARDWARE_HANDSHAKE 1
+
+ /*
+ * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+ * All rights reserved. www.lanmedia.com
+ *
+ * This code is written by:
+ * Andrew Stanley-Jones (asj@cban.com)
+ * Rob Braun (bbraun@vix.com),
+ * Michael Graff (explorer@vix.com) and
+ * Matt Thomas (matt@3am-software.com).
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License version 2, incorporated herein by reference.
+ */
+
+/*
+ * For lack of a better place, put the SSI cable stuff here.
+ */
+char *lmc_t1_cables[] = {
+ "V.10/RS423", "EIA530A", "reserved", "X.21", "V.35",
+ "EIA449/EIA530/V.36", "V.28/EIA232", "none", NULL
+};
+
+/*
+ * protocol independent method.
+ */
+static void lmc_set_protocol (lmc_softc_t * const, lmc_ctl_t *);
+
+/*
+ * media independent methods to check on media status, link, light LEDs,
+ * etc.
+ */
+static void lmc_ds3_init (lmc_softc_t * const);
+static void lmc_ds3_default (lmc_softc_t * const);
+static void lmc_ds3_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static void lmc_ds3_set_100ft (lmc_softc_t * const, int);
+static int lmc_ds3_get_link_status (lmc_softc_t * const);
+static void lmc_ds3_set_crc_length (lmc_softc_t * const, int);
+static void lmc_ds3_set_scram (lmc_softc_t * const, int);
+static void lmc_ds3_watchdog (lmc_softc_t * const);
+
+static void lmc_hssi_init (lmc_softc_t * const);
+static void lmc_hssi_default (lmc_softc_t * const);
+static void lmc_hssi_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static void lmc_hssi_set_clock (lmc_softc_t * const, int);
+static int lmc_hssi_get_link_status (lmc_softc_t * const);
+static void lmc_hssi_set_link_status (lmc_softc_t * const, int);
+static void lmc_hssi_set_crc_length (lmc_softc_t * const, int);
+static void lmc_hssi_watchdog (lmc_softc_t * const);
+
+static void lmc_ssi_init (lmc_softc_t * const);
+static void lmc_ssi_default (lmc_softc_t * const);
+static void lmc_ssi_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static void lmc_ssi_set_clock (lmc_softc_t * const, int);
+static void lmc_ssi_set_speed (lmc_softc_t * const, lmc_ctl_t *);
+static int lmc_ssi_get_link_status (lmc_softc_t * const);
+static void lmc_ssi_set_link_status (lmc_softc_t * const, int);
+static void lmc_ssi_set_crc_length (lmc_softc_t * const, int);
+static void lmc_ssi_watchdog (lmc_softc_t * const);
+
+static void lmc_t1_init (lmc_softc_t * const);
+static void lmc_t1_default (lmc_softc_t * const);
+static void lmc_t1_set_status (lmc_softc_t * const, lmc_ctl_t *);
+static int lmc_t1_get_link_status (lmc_softc_t * const);
+static void lmc_t1_set_circuit_type (lmc_softc_t * const, int);
+static void lmc_t1_set_crc_length (lmc_softc_t * const, int);
+static void lmc_t1_set_clock (lmc_softc_t * const, int);
+static void lmc_t1_watchdog (lmc_softc_t * const);
+
+static void lmc_dummy_set_1 (lmc_softc_t * const, int);
+static void lmc_dummy_set2_1 (lmc_softc_t * const, lmc_ctl_t *);
+
+static inline void write_av9110_bit (lmc_softc_t *, int);
+static void write_av9110 (lmc_softc_t *, u_int32_t, u_int32_t, u_int32_t,
+ u_int32_t, u_int32_t);
+
+lmc_media_t lmc_ds3_media = {
+ lmc_ds3_init, /* special media init stuff */
+ lmc_ds3_default, /* reset to default state */
+ lmc_ds3_set_status, /* reset status to state provided */
+ lmc_dummy_set_1, /* set clock source */
+ lmc_dummy_set2_1, /* set line speed */
+ lmc_ds3_set_100ft, /* set cable length */
+ lmc_ds3_set_scram, /* set scrambler */
+ lmc_ds3_get_link_status, /* get link status */
+ lmc_dummy_set_1, /* set link status */
+ lmc_ds3_set_crc_length, /* set CRC length */
+ lmc_dummy_set_1, /* set T1 or E1 circuit type */
+ lmc_ds3_watchdog
+};
+
+lmc_media_t lmc_hssi_media = {
+ lmc_hssi_init, /* special media init stuff */
+ lmc_hssi_default, /* reset to default state */
+ lmc_hssi_set_status, /* reset status to state provided */
+ lmc_hssi_set_clock, /* set clock source */
+ lmc_dummy_set2_1, /* set line speed */
+ lmc_dummy_set_1, /* set cable length */
+ lmc_dummy_set_1, /* set scrambler */
+ lmc_hssi_get_link_status, /* get link status */
+ lmc_hssi_set_link_status, /* set link status */
+ lmc_hssi_set_crc_length, /* set CRC length */
+ lmc_dummy_set_1, /* set T1 or E1 circuit type */
+ lmc_hssi_watchdog
+};
+
+lmc_media_t lmc_ssi_media = { lmc_ssi_init, /* special media init stuff */
+ lmc_ssi_default, /* reset to default state */
+ lmc_ssi_set_status, /* reset status to state provided */
+ lmc_ssi_set_clock, /* set clock source */
+ lmc_ssi_set_speed, /* set line speed */
+ lmc_dummy_set_1, /* set cable length */
+ lmc_dummy_set_1, /* set scrambler */
+ lmc_ssi_get_link_status, /* get link status */
+ lmc_ssi_set_link_status, /* set link status */
+ lmc_ssi_set_crc_length, /* set CRC length */
+ lmc_dummy_set_1, /* set T1 or E1 circuit type */
+ lmc_ssi_watchdog
+};
+
+lmc_media_t lmc_t1_media = {
+ lmc_t1_init, /* special media init stuff */
+ lmc_t1_default, /* reset to default state */
+ lmc_t1_set_status, /* reset status to state provided */
+ lmc_t1_set_clock, /* set clock source */
+ lmc_dummy_set2_1, /* set line speed */
+ lmc_dummy_set_1, /* set cable length */
+ lmc_dummy_set_1, /* set scrambler */
+ lmc_t1_get_link_status, /* get link status */
+ lmc_dummy_set_1, /* set link status */
+ lmc_t1_set_crc_length, /* set CRC length */
+ lmc_t1_set_circuit_type, /* set T1 or E1 circuit type */
+ lmc_t1_watchdog
+};
+
+static void
+lmc_dummy_set_1 (lmc_softc_t * const sc, int a)
+{
+}
+
+static void
+lmc_dummy_set2_1 (lmc_softc_t * const sc, lmc_ctl_t * a)
+{
+}
+
+/*
+ * HSSI methods
+ */
+
+static void
+lmc_hssi_init (lmc_softc_t * const sc)
+{
+ sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC5200;
+
+ lmc_gpio_mkoutput (sc, LMC_GEP_HSSI_CLOCK);
+}
+
+static void
+lmc_hssi_default (lmc_softc_t * const sc)
+{
+ sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+
+ sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+ sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+ sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+}
+
+/*
+ * Given a user provided state, set ourselves up to match it. This will
+ * always reset the card if needed.
+ */
+static void
+lmc_hssi_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+ if (ctl == NULL)
+ {
+ sc->lmc_media->set_clock_source (sc, sc->ictl.clock_source);
+ lmc_set_protocol (sc, NULL);
+
+ return;
+ }
+
+ /*
+ * check for change in clock source
+ */
+ if (ctl->clock_source && !sc->ictl.clock_source)
+ {
+ sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_INT);
+ sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_INT;
+ }
+ else if (!ctl->clock_source && sc->ictl.clock_source)
+ {
+ sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_EXT;
+ sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+ }
+
+ lmc_set_protocol (sc, ctl);
+}
+
+/*
+ * 1 == internal, 0 == external
+ */
+static void
+lmc_hssi_set_clock (lmc_softc_t * const sc, int ie)
+{
+ int old;
+ old = sc->ictl.clock_source;
+ if (ie == LMC_CTL_CLOCK_SOURCE_EXT)
+ {
+ sc->lmc_gpio |= LMC_GEP_HSSI_CLOCK;
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_EXT;
+ if(old != ie)
+ printk (LMC_PRINTF_FMT ": clock external\n", LMC_PRINTF_ARGS);
+ }
+ else
+ {
+ sc->lmc_gpio &= ~(LMC_GEP_HSSI_CLOCK);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+ if(old != ie)
+ printk (LMC_PRINTF_FMT ": clock internal\n", LMC_PRINTF_ARGS);
+ }
+}
+
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */
+static int
+lmc_hssi_get_link_status (lmc_softc_t * const sc)
+{
+ /*
+ * We're using the same code as SSI since
+ * they're practically the same
+ */
+ return lmc_ssi_get_link_status(sc);
+}
+
+static void
+lmc_hssi_set_link_status (lmc_softc_t * const sc, int state)
+{
+ if (state == LMC_LINK_UP)
+ sc->lmc_miireg16 |= LMC_MII16_HSSI_TA;
+ else
+ sc->lmc_miireg16 &= ~LMC_MII16_HSSI_TA;
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit
+ */
+static void
+lmc_hssi_set_crc_length (lmc_softc_t * const sc, int state)
+{
+ if (state == LMC_CTL_CRC_LENGTH_32)
+ {
+ /* 32 bit */
+ sc->lmc_miireg16 |= LMC_MII16_HSSI_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+ }
+ else
+ {
+ /* 16 bit */
+ sc->lmc_miireg16 &= ~LMC_MII16_HSSI_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+ }
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+static void
+lmc_hssi_watchdog (lmc_softc_t * const sc)
+{
+ /* HSSI is blank */
+}
+
+/*
+ * DS3 methods
+ */
+
+/*
+ * Set cable length
+ */
+static void
+lmc_ds3_set_100ft (lmc_softc_t * const sc, int ie)
+{
+ if (ie == LMC_CTL_CABLE_LENGTH_GT_100FT)
+ {
+ sc->lmc_miireg16 &= ~LMC_MII16_DS3_ZERO;
+ sc->ictl.cable_length = LMC_CTL_CABLE_LENGTH_GT_100FT;
+ }
+ else if (ie == LMC_CTL_CABLE_LENGTH_LT_100FT)
+ {
+ sc->lmc_miireg16 |= LMC_MII16_DS3_ZERO;
+ sc->ictl.cable_length = LMC_CTL_CABLE_LENGTH_LT_100FT;
+ }
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+static void
+lmc_ds3_default (lmc_softc_t * const sc)
+{
+ sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+
+ sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+ sc->lmc_media->set_cable_length (sc, LMC_CTL_CABLE_LENGTH_LT_100FT);
+ sc->lmc_media->set_scrambler (sc, LMC_CTL_OFF);
+ sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+}
+
+/*
+ * Given a user provided state, set ourselves up to match it. This will
+ * always reset the card if needed.
+ */
+static void
+lmc_ds3_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+ if (ctl == NULL)
+ {
+ sc->lmc_media->set_cable_length (sc, sc->ictl.cable_length);
+ sc->lmc_media->set_scrambler (sc, sc->ictl.scrambler_onoff);
+ lmc_set_protocol (sc, NULL);
+
+ return;
+ }
+
+ /*
+ * check for change in cable length setting
+ */
+ if (ctl->cable_length && !sc->ictl.cable_length)
+ lmc_ds3_set_100ft (sc, LMC_CTL_CABLE_LENGTH_GT_100FT);
+ else if (!ctl->cable_length && sc->ictl.cable_length)
+ lmc_ds3_set_100ft (sc, LMC_CTL_CABLE_LENGTH_LT_100FT);
+
+ /*
+ * Check for change in scrambler setting (requires reset)
+ */
+ if (ctl->scrambler_onoff && !sc->ictl.scrambler_onoff)
+ lmc_ds3_set_scram (sc, LMC_CTL_ON);
+ else if (!ctl->scrambler_onoff && sc->ictl.scrambler_onoff)
+ lmc_ds3_set_scram (sc, LMC_CTL_OFF);
+
+ lmc_set_protocol (sc, ctl);
+}
+
+static void
+lmc_ds3_init (lmc_softc_t * const sc)
+{
+ int i;
+
+ sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC5245;
+
+ /* writes zeros everywhere */
+ for (i = 0; i < 21; i++)
+ {
+ lmc_mii_writereg (sc, 0, 17, i);
+ lmc_mii_writereg (sc, 0, 18, 0);
+ }
+
+ /* set some essential bits */
+ lmc_mii_writereg (sc, 0, 17, 1);
+ lmc_mii_writereg (sc, 0, 18, 0x25); /* ser, xtx */
+
+ lmc_mii_writereg (sc, 0, 17, 5);
+ lmc_mii_writereg (sc, 0, 18, 0x80); /* emode */
+
+ lmc_mii_writereg (sc, 0, 17, 14);
+ lmc_mii_writereg (sc, 0, 18, 0x30); /* rcgen, tcgen */
+
+ /* clear counters and latched bits */
+ for (i = 0; i < 21; i++)
+ {
+ lmc_mii_writereg (sc, 0, 17, i);
+ lmc_mii_readreg (sc, 0, 18);
+ }
+}
+
+/*
+ * 1 == DS3 payload scrambled, 0 == not scrambled
+ */
+static void
+lmc_ds3_set_scram (lmc_softc_t * const sc, int ie)
+{
+ if (ie == LMC_CTL_ON)
+ {
+ sc->lmc_miireg16 |= LMC_MII16_DS3_SCRAM;
+ sc->ictl.scrambler_onoff = LMC_CTL_ON;
+ }
+ else
+ {
+ sc->lmc_miireg16 &= ~LMC_MII16_DS3_SCRAM;
+ sc->ictl.scrambler_onoff = LMC_CTL_OFF;
+ }
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */
+static int
+lmc_ds3_get_link_status (lmc_softc_t * const sc)
+{
+ u_int16_t link_status, link_status_11;
+ int ret = 1;
+
+ lmc_mii_writereg (sc, 0, 17, 7);
+ link_status = lmc_mii_readreg (sc, 0, 18);
+
+ /* LMC5245 (DS3) & LMC1200 (DS1) LED definitions
+ * led0 yellow = far-end adapter is in Red alarm condition
+ * led1 blue = received an Alarm Indication signal
+ * (upstream failure)
+ * led2 Green = power to adapter, Gate Array loaded & driver
+ * attached
+ * led3 red = Loss of Signal (LOS) or out of frame (OOF)
+ * conditions detected on T3 receive signal
+ */
+
+ lmc_led_on(sc, LMC_DS3_LED2);
+
+ if ((link_status & LMC_FRAMER_REG0_DLOS) ||
+ (link_status & LMC_FRAMER_REG0_OOFS)){
+ ret = 0;
+ if(sc->last_led_err[3] != 1){
+ u16 r1;
+ lmc_mii_writereg (sc, 0, 17, 01); /* Turn on Xbit error as our cisco does */
+ r1 = lmc_mii_readreg (sc, 0, 18);
+ r1 &= 0xfe;
+ lmc_mii_writereg(sc, 0, 18, r1);
+ printk(KERN_WARNING "%s: Red Alarm - Loss of Signal or Loss of Framing\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED3); /* turn on red LED */
+ sc->last_led_err[3] = 1;
+ }
+ else {
+ lmc_led_off(sc, LMC_DS3_LED3); /* turn on red LED */
+ if(sc->last_led_err[3] == 1){
+ u16 r1;
+ lmc_mii_writereg (sc, 0, 17, 01); /* Turn off Xbit error */
+ r1 = lmc_mii_readreg (sc, 0, 18);
+ r1 |= 0x01;
+ lmc_mii_writereg(sc, 0, 18, r1);
+ }
+ sc->last_led_err[3] = 0;
+ }
+
+ lmc_mii_writereg(sc, 0, 17, 0x10);
+ link_status_11 = lmc_mii_readreg(sc, 0, 18);
+ if((link_status & LMC_FRAMER_REG0_AIS) ||
+ (link_status_11 & LMC_FRAMER_REG10_XBIT)) {
+ ret = 0;
+ if(sc->last_led_err[0] != 1){
+ printk(KERN_WARNING "%s: AIS Alarm or XBit Error\n", sc->name);
+ printk(KERN_WARNING "%s: Remote end has loss of signal or framing\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED0);
+ sc->last_led_err[0] = 1;
+ }
+ else {
+ lmc_led_off(sc, LMC_DS3_LED0);
+ sc->last_led_err[0] = 0;
+ }
+
+ lmc_mii_writereg (sc, 0, 17, 9);
+ link_status = lmc_mii_readreg (sc, 0, 18);
+
+ if(link_status & LMC_FRAMER_REG9_RBLUE){
+ ret = 0;
+ if(sc->last_led_err[1] != 1){
+ printk(KERN_WARNING "%s: Blue Alarm - Receiving all 1's\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED1);
+ sc->last_led_err[1] = 1;
+ }
+ else {
+ lmc_led_off(sc, LMC_DS3_LED1);
+ sc->last_led_err[1] = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit
+ */
+static void
+lmc_ds3_set_crc_length (lmc_softc_t * const sc, int state)
+{
+ if (state == LMC_CTL_CRC_LENGTH_32)
+ {
+ /* 32 bit */
+ sc->lmc_miireg16 |= LMC_MII16_DS3_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+ }
+ else
+ {
+ /* 16 bit */
+ sc->lmc_miireg16 &= ~LMC_MII16_DS3_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+ }
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+static void
+lmc_ds3_watchdog (lmc_softc_t * const sc)
+{
+
+}
+
+
+/*
+ * SSI methods
+ */
+
+static void
+lmc_ssi_init (lmc_softc_t * const sc)
+{
+ u_int16_t mii17;
+ int cable;
+
+ sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC1000;
+
+ mii17 = lmc_mii_readreg (sc, 0, 17);
+
+ cable = (mii17 & LMC_MII17_SSI_CABLE_MASK) >> LMC_MII17_SSI_CABLE_SHIFT;
+ sc->ictl.cable_type = cable;
+
+ lmc_gpio_mkoutput (sc, LMC_GEP_SSI_TXCLOCK);
+}
+
+static void
+lmc_ssi_default (lmc_softc_t * const sc)
+{
+ sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+
+ /*
+ * make TXCLOCK always be an output
+ */
+ lmc_gpio_mkoutput (sc, LMC_GEP_SSI_TXCLOCK);
+
+ sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+ sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+ sc->lmc_media->set_speed (sc, NULL);
+ sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+}
+
+/*
+ * Given a user provided state, set ourselves up to match it. This will
+ * always reset the card if needed.
+ */
+static void
+lmc_ssi_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+ if (ctl == NULL)
+ {
+ sc->lmc_media->set_clock_source (sc, sc->ictl.clock_source);
+ sc->lmc_media->set_speed (sc, &sc->ictl);
+ lmc_set_protocol (sc, NULL);
+
+ return;
+ }
+
+ /*
+ * check for change in clock source
+ */
+ if (ctl->clock_source == LMC_CTL_CLOCK_SOURCE_INT
+ && sc->ictl.clock_source == LMC_CTL_CLOCK_SOURCE_EXT)
+ {
+ sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_INT);
+ sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_INT;
+ }
+ else if (ctl->clock_source == LMC_CTL_CLOCK_SOURCE_EXT
+ && sc->ictl.clock_source == LMC_CTL_CLOCK_SOURCE_INT)
+ {
+ sc->lmc_media->set_clock_source (sc, LMC_CTL_CLOCK_SOURCE_EXT);
+ sc->lmc_timing = LMC_CTL_CLOCK_SOURCE_EXT;
+ }
+
+ if (ctl->clock_rate != sc->ictl.clock_rate)
+ sc->lmc_media->set_speed (sc, ctl);
+
+ lmc_set_protocol (sc, ctl);
+}
+
+/*
+ * 1 == internal, 0 == external
+ */
+static void
+lmc_ssi_set_clock (lmc_softc_t * const sc, int ie)
+{
+ int old;
+ old = ie;
+ if (ie == LMC_CTL_CLOCK_SOURCE_EXT)
+ {
+ sc->lmc_gpio &= ~(LMC_GEP_SSI_TXCLOCK);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_EXT;
+ if(ie != old)
+ printk (LMC_PRINTF_FMT ": clock external\n", LMC_PRINTF_ARGS);
+ }
+ else
+ {
+ sc->lmc_gpio |= LMC_GEP_SSI_TXCLOCK;
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+ if(ie != old)
+ printk (LMC_PRINTF_FMT ": clock internal\n", LMC_PRINTF_ARGS);
+ }
+}
+
+static void
+lmc_ssi_set_speed (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+ lmc_ctl_t *ictl = &sc->ictl;
+ lmc_av9110_t *av;
+
+ /* original settings for clock rate of:
+ * 100 Khz (8,25,0,0,2) were incorrect
+ * they should have been 80,125,1,3,3
+ * There are 17 param combinations to produce this freq.
+ * For 1.5 Mhz use 120,100,1,1,2 (226 param. combinations)
+ */
+ if (ctl == NULL)
+ {
+ av = &ictl->cardspec.ssi;
+ ictl->clock_rate = 1500000;
+ av->f = ictl->clock_rate;
+ av->n = 120;
+ av->m = 100;
+ av->v = 1;
+ av->x = 1;
+ av->r = 2;
+
+ write_av9110 (sc, av->n, av->m, av->v, av->x, av->r);
+ return;
+ }
+
+ av = &ctl->cardspec.ssi;
+
+ if (av->f == 0)
+ return;
+
+ ictl->clock_rate = av->f; /* really, this is the rate we are */
+ ictl->cardspec.ssi = *av;
+
+ write_av9110 (sc, av->n, av->m, av->v, av->x, av->r);
+}
+
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */
+static int
+lmc_ssi_get_link_status (lmc_softc_t * const sc)
+{
+ u_int16_t link_status;
+ u_int32_t ticks;
+ int ret = 1;
+ int hw_hdsk = 1;
+
+ /*
+ * missing CTS? Hmm. If we require CTS on, we may never get the
+ * link to come up, so omit it in this test.
+ *
+ * Also, it seems that with a loopback cable, DCD isn't asserted,
+ * so just check for things like this:
+ * DSR _must_ be asserted.
+ * One of DCD or CTS must be asserted.
+ */
+
+ /* LMC 1000 (SSI) LED definitions
+ * led0 Green = power to adapter, Gate Array loaded &
+ * driver attached
+ * led1 Green = DSR and DTR and RTS and CTS are set
+ * led2 Green = Cable detected
+ * led3 red = No timing is available from the
+ * cable or the on-board frequency
+ * generator.
+ */
+
+ link_status = lmc_mii_readreg (sc, 0, 16);
+
+ /* Is the transmit clock still available */
+ ticks = LMC_CSR_READ (sc, csr_gp_timer);
+ ticks = 0x0000ffff - (ticks & 0x0000ffff);
+
+ lmc_led_on (sc, LMC_MII16_LED0);
+
+ /* ====== transmit clock determination ===== */
+ if (sc->lmc_timing == LMC_CTL_CLOCK_SOURCE_INT) {
+ lmc_led_off(sc, LMC_MII16_LED3);
+ }
+ else if (ticks == 0 ) { /* no clock found ? */
+ ret = 0;
+ if(sc->last_led_err[3] != 1){
+ sc->stats.tx_lossOfClockCnt++;
+ printk(KERN_WARNING "%s: Lost Clock, Link Down\n", sc->name);
+ }
+ sc->last_led_err[3] = 1;
+ lmc_led_on (sc, LMC_MII16_LED3); /* turn ON red LED */
+ }
+ else {
+ if(sc->last_led_err[3] == 1)
+ printk(KERN_WARNING "%s: Clock Returned\n", sc->name);
+ sc->last_led_err[3] = 0;
+ lmc_led_off (sc, LMC_MII16_LED3); /* turn OFF red LED */
+ }
+
+ if ((link_status & LMC_MII16_SSI_DSR) == 0) { /* Also HSSI CA */
+ ret = 0;
+ hw_hdsk = 0;
+ }
+
+#ifdef CONFIG_LMC_IGNORE_HARDWARE_HANDSHAKE
+ if ((link_status & (LMC_MII16_SSI_CTS | LMC_MII16_SSI_DCD)) == 0){
+ ret = 0;
+ hw_hdsk = 0;
+ }
+#endif
+
+ if(hw_hdsk == 0){
+ if(sc->last_led_err[1] != 1)
+ printk(KERN_WARNING "%s: DSR not asserted\n", sc->name);
+ sc->last_led_err[1] = 1;
+ lmc_led_off(sc, LMC_MII16_LED1);
+ }
+ else {
+ if(sc->last_led_err[1] != 0)
+ printk(KERN_WARNING "%s: DSR now asserted\n", sc->name);
+ sc->last_led_err[1] = 0;
+ lmc_led_on(sc, LMC_MII16_LED1);
+ }
+
+ if(ret == 1) {
+ lmc_led_on(sc, LMC_MII16_LED2); /* Over all good status? */
+ }
+
+ return ret;
+}
+
+static void
+lmc_ssi_set_link_status (lmc_softc_t * const sc, int state)
+{
+ if (state == LMC_LINK_UP)
+ {
+ sc->lmc_miireg16 |= (LMC_MII16_SSI_DTR | LMC_MII16_SSI_RTS);
+ printk (LMC_PRINTF_FMT ": asserting DTR and RTS\n", LMC_PRINTF_ARGS);
+ }
+ else
+ {
+ sc->lmc_miireg16 &= ~(LMC_MII16_SSI_DTR | LMC_MII16_SSI_RTS);
+ printk (LMC_PRINTF_FMT ": deasserting DTR and RTS\n", LMC_PRINTF_ARGS);
+ }
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit
+ */
+static void
+lmc_ssi_set_crc_length (lmc_softc_t * const sc, int state)
+{
+ if (state == LMC_CTL_CRC_LENGTH_32)
+ {
+ /* 32 bit */
+ sc->lmc_miireg16 |= LMC_MII16_SSI_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+ sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_4;
+
+ }
+ else
+ {
+ /* 16 bit */
+ sc->lmc_miireg16 &= ~LMC_MII16_SSI_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+ sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_2;
+ }
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * These are bits to program the ssi frequency generator
+ */
+static inline void
+write_av9110_bit (lmc_softc_t * sc, int c)
+{
+ /*
+ * set the data bit as we need it.
+ */
+ sc->lmc_gpio &= ~(LMC_GEP_CLK);
+ if (c & 0x01)
+ sc->lmc_gpio |= LMC_GEP_DATA;
+ else
+ sc->lmc_gpio &= ~(LMC_GEP_DATA);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+ /*
+ * set the clock to high
+ */
+ sc->lmc_gpio |= LMC_GEP_CLK;
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+ /*
+ * set the clock to low again.
+ */
+ sc->lmc_gpio &= ~(LMC_GEP_CLK);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+}
+
+static void
+write_av9110 (lmc_softc_t * sc, u_int32_t n, u_int32_t m, u_int32_t v,
+ u_int32_t x, u_int32_t r)
+{
+ int i;
+
+#if 0
+ printk (LMC_PRINTF_FMT ": speed %u, %d %d %d %d %d\n",
+ LMC_PRINTF_ARGS, sc->ictl.clock_rate, n, m, v, x, r);
+#endif
+
+ sc->lmc_gpio |= LMC_GEP_SSI_GENERATOR;
+ sc->lmc_gpio &= ~(LMC_GEP_DATA | LMC_GEP_CLK);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+ /*
+ * Set the TXCLOCK, GENERATOR, SERIAL, and SERIALCLK
+ * as outputs.
+ */
+ lmc_gpio_mkoutput (sc, (LMC_GEP_DATA | LMC_GEP_CLK
+ | LMC_GEP_SSI_GENERATOR));
+
+ sc->lmc_gpio &= ~(LMC_GEP_SSI_GENERATOR);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+
+ /*
+ * a shifting we will go...
+ */
+ for (i = 0; i < 7; i++)
+ write_av9110_bit (sc, n >> i);
+ for (i = 0; i < 7; i++)
+ write_av9110_bit (sc, m >> i);
+ for (i = 0; i < 1; i++)
+ write_av9110_bit (sc, v >> i);
+ for (i = 0; i < 2; i++)
+ write_av9110_bit (sc, x >> i);
+ for (i = 0; i < 2; i++)
+ write_av9110_bit (sc, r >> i);
+ for (i = 0; i < 5; i++)
+ write_av9110_bit (sc, 0x17 >> i);
+
+ /*
+ * stop driving serial-related signals
+ */
+ lmc_gpio_mkinput (sc,
+ (LMC_GEP_DATA | LMC_GEP_CLK
+ | LMC_GEP_SSI_GENERATOR));
+}
+
+static void
+lmc_ssi_watchdog (lmc_softc_t * const sc)
+{
+ u_int16_t mii17;
+ struct ssicsr2
+ {
+ unsigned short dtr:1, dsr:1, rts:1, cable:3, crc:1, led0:1, led1:1,
+ led2:1, led3:1, fifo:1, ll:1, rl:1, tm:1, loop:1;
+ };
+ struct ssicsr2 *ssicsr;
+ mii17 = lmc_mii_readreg (sc, 0, 17);
+ ssicsr = (struct ssicsr2 *) &mii17;
+ if (ssicsr->cable == 7)
+ {
+ lmc_led_off (sc, LMC_MII16_LED2);
+ }
+ else
+ {
+ lmc_led_on (sc, LMC_MII16_LED2);
+ }
+
+}
+
+/*
+ * T1 methods
+ */
+
+/*
+ * The framer regs are multiplexed through MII regs 17 & 18
+ * write the register address to MII reg 17 and the * data to MII reg 18. */
+static void
+lmc_t1_write (lmc_softc_t * const sc, int a, int d)
+{
+ lmc_mii_writereg (sc, 0, 17, a);
+ lmc_mii_writereg (sc, 0, 18, d);
+}
+
+/* Save a warning
+static int
+lmc_t1_read (lmc_softc_t * const sc, int a)
+{
+ lmc_mii_writereg (sc, 0, 17, a);
+ return lmc_mii_readreg (sc, 0, 18);
+}
+*/
+
+
+static void
+lmc_t1_init (lmc_softc_t * const sc)
+{
+ u_int16_t mii16;
+ int i;
+
+ sc->ictl.cardtype = LMC_CTL_CARDTYPE_LMC1200;
+ mii16 = lmc_mii_readreg (sc, 0, 16);
+
+ /* reset 8370 */
+ mii16 &= ~LMC_MII16_T1_RST;
+ lmc_mii_writereg (sc, 0, 16, mii16 | LMC_MII16_T1_RST);
+ lmc_mii_writereg (sc, 0, 16, mii16);
+
+ /* set T1 or E1 line. Uses sc->lmcmii16 reg in function so update it */
+ sc->lmc_miireg16 = mii16;
+ lmc_t1_set_circuit_type(sc, LMC_CTL_CIRCUIT_TYPE_T1);
+ mii16 = sc->lmc_miireg16;
+
+ lmc_t1_write (sc, 0x01, 0x1B); /* CR0 - primary control */
+ lmc_t1_write (sc, 0x02, 0x42); /* JAT_CR - jitter atten config */
+ lmc_t1_write (sc, 0x14, 0x00); /* LOOP - loopback config */
+ lmc_t1_write (sc, 0x15, 0x00); /* DL3_TS - external data link timeslot */
+ lmc_t1_write (sc, 0x18, 0xFF); /* PIO - programmable I/O */
+ lmc_t1_write (sc, 0x19, 0x30); /* POE - programmable OE */
+ lmc_t1_write (sc, 0x1A, 0x0F); /* CMUX - clock input mux */
+ lmc_t1_write (sc, 0x20, 0x41); /* LIU_CR - RX LIU config */
+ lmc_t1_write (sc, 0x22, 0x76); /* RLIU_CR - RX LIU config */
+ lmc_t1_write (sc, 0x40, 0x03); /* RCR0 - RX config */
+ lmc_t1_write (sc, 0x45, 0x00); /* RALM - RX alarm config */
+ lmc_t1_write (sc, 0x46, 0x05); /* LATCH - RX alarm/err/cntr latch */
+ lmc_t1_write (sc, 0x68, 0x40); /* TLIU_CR - TX LIU config */
+ lmc_t1_write (sc, 0x70, 0x0D); /* TCR0 - TX framer config */
+ lmc_t1_write (sc, 0x71, 0x05); /* TCR1 - TX config */
+ lmc_t1_write (sc, 0x72, 0x0B); /* TFRM - TX frame format */
+ lmc_t1_write (sc, 0x73, 0x00); /* TERROR - TX error insert */
+ lmc_t1_write (sc, 0x74, 0x00); /* TMAN - TX manual Sa/FEBE config */
+ lmc_t1_write (sc, 0x75, 0x00); /* TALM - TX alarm signal config */
+ lmc_t1_write (sc, 0x76, 0x00); /* TPATT - TX test pattern config */
+ lmc_t1_write (sc, 0x77, 0x00); /* TLB - TX inband loopback config */
+ lmc_t1_write (sc, 0x90, 0x05); /* CLAD_CR - clock rate adapter config */
+ lmc_t1_write (sc, 0x91, 0x05); /* CSEL - clad freq sel */
+ lmc_t1_write (sc, 0xA6, 0x00); /* DL1_CTL - DL1 control */
+ lmc_t1_write (sc, 0xB1, 0x00); /* DL2_CTL - DL2 control */
+ lmc_t1_write (sc, 0xD0, 0x47); /* SBI_CR - sys bus iface config */
+ lmc_t1_write (sc, 0xD1, 0x70); /* RSB_CR - RX sys bus config */
+ lmc_t1_write (sc, 0xD4, 0x30); /* TSB_CR - TX sys bus config */
+ for (i = 0; i < 32; i++)
+ {
+ lmc_t1_write (sc, 0x0E0 + i, 0x00); /* SBCn - sys bus per-channel ctl */
+ lmc_t1_write (sc, 0x100 + i, 0x00); /* TPCn - TX per-channel ctl */
+ lmc_t1_write (sc, 0x180 + i, 0x00); /* RPCn - RX per-channel ctl */
+ }
+ for (i = 1; i < 25; i++)
+ {
+ lmc_t1_write (sc, 0x0E0 + i, 0x0D); /* SBCn - sys bus per-channel ctl */
+ }
+
+ mii16 |= LMC_MII16_T1_XOE;
+ lmc_mii_writereg (sc, 0, 16, mii16);
+ sc->lmc_miireg16 = mii16;
+}
+
+static void
+lmc_t1_default (lmc_softc_t * const sc)
+{
+ sc->lmc_miireg16 = LMC_MII16_LED_ALL;
+ sc->lmc_media->set_link_status (sc, LMC_LINK_DOWN);
+ sc->lmc_media->set_circuit_type (sc, LMC_CTL_CIRCUIT_TYPE_T1);
+ sc->lmc_media->set_crc_length (sc, LMC_CTL_CRC_LENGTH_16);
+ /* Right now we can only clock from out internal source */
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+}
+/* * Given a user provided state, set ourselves up to match it. This will * always reset the card if needed.
+ */
+static void
+lmc_t1_set_status (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+ if (ctl == NULL)
+ {
+ sc->lmc_media->set_circuit_type (sc, sc->ictl.circuit_type);
+ lmc_set_protocol (sc, NULL);
+
+ return;
+ }
+ /*
+ * check for change in circuit type */
+ if (ctl->circuit_type == LMC_CTL_CIRCUIT_TYPE_T1
+ && sc->ictl.circuit_type ==
+ LMC_CTL_CIRCUIT_TYPE_E1) sc->lmc_media->set_circuit_type (sc,
+ LMC_CTL_CIRCUIT_TYPE_E1);
+ else if (ctl->circuit_type == LMC_CTL_CIRCUIT_TYPE_E1
+ && sc->ictl.circuit_type == LMC_CTL_CIRCUIT_TYPE_T1)
+ sc->lmc_media->set_circuit_type (sc, LMC_CTL_CIRCUIT_TYPE_T1);
+ lmc_set_protocol (sc, ctl);
+}
+/*
+ * return hardware link status.
+ * 0 == link is down, 1 == link is up.
+ */ static int
+lmc_t1_get_link_status (lmc_softc_t * const sc)
+{
+ u_int16_t link_status;
+ int ret = 1;
+
+ /* LMC5245 (DS3) & LMC1200 (DS1) LED definitions
+ * led0 yellow = far-end adapter is in Red alarm condition
+ * led1 blue = received an Alarm Indication signal
+ * (upstream failure)
+ * led2 Green = power to adapter, Gate Array loaded & driver
+ * attached
+ * led3 red = Loss of Signal (LOS) or out of frame (OOF)
+ * conditions detected on T3 receive signal
+ */
+ lmc_trace(sc->lmc_device, "lmc_t1_get_link_status in");
+ lmc_led_on(sc, LMC_DS3_LED2);
+
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_ALARM1_STATUS);
+ link_status = lmc_mii_readreg (sc, 0, 18);
+
+
+ if (link_status & T1F_RAIS) { /* turn on blue LED */
+ ret = 0;
+ if(sc->last_led_err[1] != 1){
+ printk(KERN_WARNING "%s: Receive AIS/Blue Alarm. Far end in RED alarm\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED1);
+ sc->last_led_err[1] = 1;
+ }
+ else {
+ if(sc->last_led_err[1] != 0){
+ printk(KERN_WARNING "%s: End AIS/Blue Alarm\n", sc->name);
+ }
+ lmc_led_off (sc, LMC_DS3_LED1);
+ sc->last_led_err[1] = 0;
+ }
+
+ /*
+ * Yellow Alarm is nasty evil stuff, looks at data patterns
+ * inside the channel and confuses it with HDLC framing
+ * ignore all yellow alarms.
+ *
+ * Do listen to MultiFrame Yellow alarm which while implemented
+ * different ways isn't in the channel and hence somewhat
+ * more reliable
+ */
+
+ if (link_status & T1F_RMYEL) {
+ ret = 0;
+ if(sc->last_led_err[0] != 1){
+ printk(KERN_WARNING "%s: Receive Yellow AIS Alarm\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED0);
+ sc->last_led_err[0] = 1;
+ }
+ else {
+ if(sc->last_led_err[0] != 0){
+ printk(KERN_WARNING "%s: End of Yellow AIS Alarm\n", sc->name);
+ }
+ lmc_led_off(sc, LMC_DS3_LED0);
+ sc->last_led_err[0] = 0;
+ }
+
+ /*
+ * Loss of signal and los of frame
+ * Use the green bit to identify which one lit the led
+ */
+ if(link_status & T1F_RLOF){
+ ret = 0;
+ if(sc->last_led_err[3] != 1){
+ printk(KERN_WARNING "%s: Local Red Alarm: Loss of Framing\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED3);
+ sc->last_led_err[3] = 1;
+
+ }
+ else {
+ if(sc->last_led_err[3] != 0){
+ printk(KERN_WARNING "%s: End Red Alarm (LOF)\n", sc->name);
+ }
+ if( ! (link_status & T1F_RLOS))
+ lmc_led_off(sc, LMC_DS3_LED3);
+ sc->last_led_err[3] = 0;
+ }
+
+ if(link_status & T1F_RLOS){
+ ret = 0;
+ if(sc->last_led_err[2] != 1){
+ printk(KERN_WARNING "%s: Local Red Alarm: Loss of Signal\n", sc->name);
+ }
+ lmc_led_on(sc, LMC_DS3_LED3);
+ sc->last_led_err[2] = 1;
+
+ }
+ else {
+ if(sc->last_led_err[2] != 0){
+ printk(KERN_WARNING "%s: End Red Alarm (LOS)\n", sc->name);
+ }
+ if( ! (link_status & T1F_RLOF))
+ lmc_led_off(sc, LMC_DS3_LED3);
+ sc->last_led_err[2] = 0;
+ }
+
+ sc->lmc_xinfo.t1_alarm1_status = link_status;
+
+ lmc_mii_writereg (sc, 0, 17, T1FRAMER_ALARM2_STATUS);
+ sc->lmc_xinfo.t1_alarm2_status = lmc_mii_readreg (sc, 0, 18);
+
+
+ lmc_trace(sc->lmc_device, "lmc_t1_get_link_status out");
+
+ return ret;
+}
+
+/*
+ * 1 == T1 Circuit Type , 0 == E1 Circuit Type
+ */
+static void
+lmc_t1_set_circuit_type (lmc_softc_t * const sc, int ie)
+{
+ if (ie == LMC_CTL_CIRCUIT_TYPE_T1) {
+ sc->lmc_miireg16 |= LMC_MII16_T1_Z;
+ sc->ictl.circuit_type = LMC_CTL_CIRCUIT_TYPE_T1;
+ printk(KERN_INFO "%s: In T1 Mode\n", sc->name);
+ }
+ else {
+ sc->lmc_miireg16 &= ~LMC_MII16_T1_Z;
+ sc->ictl.circuit_type = LMC_CTL_CIRCUIT_TYPE_E1;
+ printk(KERN_INFO "%s: In E1 Mode\n", sc->name);
+ }
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+
+}
+
+/*
+ * 0 == 16bit, 1 == 32bit */
+static void
+lmc_t1_set_crc_length (lmc_softc_t * const sc, int state)
+{
+ if (state == LMC_CTL_CRC_LENGTH_32)
+ {
+ /* 32 bit */
+ sc->lmc_miireg16 |= LMC_MII16_T1_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_32;
+ sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_4;
+
+ }
+ else
+ {
+ /* 16 bit */ sc->lmc_miireg16 &= ~LMC_MII16_T1_CRC;
+ sc->ictl.crc_length = LMC_CTL_CRC_LENGTH_16;
+ sc->lmc_crcSize = LMC_CTL_CRC_BYTESIZE_2;
+
+ }
+
+ lmc_mii_writereg (sc, 0, 16, sc->lmc_miireg16);
+}
+
+/*
+ * 1 == internal, 0 == external
+ */
+static void
+lmc_t1_set_clock (lmc_softc_t * const sc, int ie)
+{
+ int old;
+ old = ie;
+ if (ie == LMC_CTL_CLOCK_SOURCE_EXT)
+ {
+ sc->lmc_gpio &= ~(LMC_GEP_SSI_TXCLOCK);
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_EXT;
+ if(old != ie)
+ printk (LMC_PRINTF_FMT ": clock external\n", LMC_PRINTF_ARGS);
+ }
+ else
+ {
+ sc->lmc_gpio |= LMC_GEP_SSI_TXCLOCK;
+ LMC_CSR_WRITE (sc, csr_gp, sc->lmc_gpio);
+ sc->ictl.clock_source = LMC_CTL_CLOCK_SOURCE_INT;
+ if(old != ie)
+ printk (LMC_PRINTF_FMT ": clock internal\n", LMC_PRINTF_ARGS);
+ }
+}
+
+static void
+lmc_t1_watchdog (lmc_softc_t * const sc)
+{
+}
+
+static void
+lmc_set_protocol (lmc_softc_t * const sc, lmc_ctl_t * ctl)
+{
+ if (ctl == 0)
+ {
+ sc->ictl.keepalive_onoff = LMC_CTL_ON;
+
+ return;
+ }
+}
diff --git a/drivers/net/wan/lmc/lmc_media.h b/drivers/net/wan/lmc/lmc_media.h
new file mode 100644
index 00000000000..ddcc0040356
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_media.h
@@ -0,0 +1,65 @@
+#ifndef _LMC_MEDIA_H_
+#define _LMC_MEDIA_H_
+
+lmc_media_t lmc_ds3_media = {
+ lmc_ds3_init, /* special media init stuff */
+ lmc_ds3_default, /* reset to default state */
+ lmc_ds3_set_status, /* reset status to state provided */
+ lmc_dummy_set_1, /* set clock source */
+ lmc_dummy_set2_1, /* set line speed */
+ lmc_ds3_set_100ft, /* set cable length */
+ lmc_ds3_set_scram, /* set scrambler */
+ lmc_ds3_get_link_status, /* get link status */
+ lmc_dummy_set_1, /* set link status */
+ lmc_ds3_set_crc_length, /* set CRC length */
+ lmc_dummy_set_1, /* set T1 or E1 circuit type */
+ lmc_ds3_watchdog
+};
+
+lmc_media_t lmc_hssi_media = {
+ lmc_hssi_init, /* special media init stuff */
+ lmc_hssi_default, /* reset to default state */
+ lmc_hssi_set_status, /* reset status to state provided */
+ lmc_hssi_set_clock, /* set clock source */
+ lmc_dummy_set2_1, /* set line speed */
+ lmc_dummy_set_1, /* set cable length */
+ lmc_dummy_set_1, /* set scrambler */
+ lmc_hssi_get_link_status, /* get link status */
+ lmc_hssi_set_link_status, /* set link status */
+ lmc_hssi_set_crc_length, /* set CRC length */
+ lmc_dummy_set_1, /* set T1 or E1 circuit type */
+ lmc_hssi_watchdog
+};
+
+lmc_media_t lmc_ssi_media = { lmc_ssi_init, /* special media init stuff */
+ lmc_ssi_default, /* reset to default state */
+ lmc_ssi_set_status, /* reset status to state provided */
+ lmc_ssi_set_clock, /* set clock source */
+ lmc_ssi_set_speed, /* set line speed */
+ lmc_dummy_set_1, /* set cable length */
+ lmc_dummy_set_1, /* set scrambler */
+ lmc_ssi_get_link_status, /* get link status */
+ lmc_ssi_set_link_status, /* set link status */
+ lmc_ssi_set_crc_length, /* set CRC length */
+ lmc_dummy_set_1, /* set T1 or E1 circuit type */
+ lmc_ssi_watchdog
+};
+
+lmc_media_t lmc_t1_media = {
+ lmc_t1_init, /* special media init stuff */
+ lmc_t1_default, /* reset to default state */
+ lmc_t1_set_status, /* reset status to state provided */
+ lmc_t1_set_clock, /* set clock source */
+ lmc_dummy_set2_1, /* set line speed */
+ lmc_dummy_set_1, /* set cable length */
+ lmc_dummy_set_1, /* set scrambler */
+ lmc_t1_get_link_status, /* get link status */
+ lmc_dummy_set_1, /* set link status */
+ lmc_t1_set_crc_length, /* set CRC length */
+ lmc_t1_set_circuit_type, /* set T1 or E1 circuit type */
+ lmc_t1_watchdog
+};
+
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_prot.h b/drivers/net/wan/lmc/lmc_prot.h
new file mode 100644
index 00000000000..f3b1df9e2cd
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_prot.h
@@ -0,0 +1,15 @@
+#ifndef _LMC_PROTO_H_
+#define _LMC_PROTO_H_
+
+void lmc_proto_init(lmc_softc_t * const)
+void lmc_proto_attach(lmc_softc_t *sc const)
+void lmc_proto_detach(lmc_softc *sc const)
+void lmc_proto_reopen(lmc_softc_t *sc const)
+int lmc_proto_ioctl(lmc_softc_t *sc const, struct ifreq *ifr, int cmd)
+void lmc_proto_open(lmc_softc_t *sc const)
+void lmc_proto_close(lmc_softc_t *sc const)
+unsigned short lmc_proto_type(lmc_softc_t *sc const, struct skbuff *skb)
+
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_proto.c b/drivers/net/wan/lmc/lmc_proto.c
new file mode 100644
index 00000000000..74876c0073e
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_proto.c
@@ -0,0 +1,249 @@
+ /*
+ * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+ * All rights reserved. www.lanmedia.com
+ *
+ * This code is written by:
+ * Andrew Stanley-Jones (asj@cban.com)
+ * Rob Braun (bbraun@vix.com),
+ * Michael Graff (explorer@vix.com) and
+ * Matt Thomas (matt@3am-software.com).
+ *
+ * With Help By:
+ * David Boggs
+ * Ron Crane
+ * Allan Cox
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License version 2, incorporated herein by reference.
+ *
+ * Driver for the LanMedia LMC5200, LMC5245, LMC1000, LMC1200 cards.
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inet.h>
+#include <linux/workqueue.h>
+#include <linux/proc_fs.h>
+#include <linux/bitops.h>
+
+#include <net/syncppp.h>
+
+#include <asm/processor.h> /* Processor type for cache alignment. */
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/smp.h>
+
+#include "lmc.h"
+#include "lmc_var.h"
+#include "lmc_debug.h"
+#include "lmc_ioctl.h"
+#include "lmc_proto.h"
+
+/*
+ * The compile-time variable SPPPSTUP causes the module to be
+ * compiled without referencing any of the sync ppp routines.
+ */
+#ifdef SPPPSTUB
+#define SPPP_detach(d) (void)0
+#define SPPP_open(d) 0
+#define SPPP_reopen(d) (void)0
+#define SPPP_close(d) (void)0
+#define SPPP_attach(d) (void)0
+#define SPPP_do_ioctl(d,i,c) -EOPNOTSUPP
+#else
+#define SPPP_attach(x) sppp_attach((x)->pd)
+#define SPPP_detach(x) sppp_detach((x)->pd->dev)
+#define SPPP_open(x) sppp_open((x)->pd->dev)
+#define SPPP_reopen(x) sppp_reopen((x)->pd->dev)
+#define SPPP_close(x) sppp_close((x)->pd->dev)
+#define SPPP_do_ioctl(x, y, z) sppp_do_ioctl((x)->pd->dev, (y), (z))
+#endif
+
+// init
+void lmc_proto_init(lmc_softc_t *sc) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_init in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ sc->pd = kmalloc(sizeof(struct ppp_device), GFP_KERNEL);
+ if (!sc->pd) {
+ printk("lmc_proto_init(): kmalloc failure!\n");
+ return;
+ }
+ sc->pd->dev = sc->lmc_device;
+ sc->if_ptr = sc->pd;
+ break;
+ case LMC_RAW:
+ break;
+ default:
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_init out");
+}
+
+// attach
+void lmc_proto_attach(lmc_softc_t *sc) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_attach in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ {
+ struct net_device *dev = sc->lmc_device;
+ SPPP_attach(sc);
+ dev->do_ioctl = lmc_ioctl;
+ }
+ break;
+ case LMC_NET:
+ {
+ struct net_device *dev = sc->lmc_device;
+ /*
+ * They set a few basics because they don't use sync_ppp
+ */
+ dev->flags |= IFF_POINTOPOINT;
+ dev->hard_header = NULL;
+ dev->hard_header_len = 0;
+ dev->addr_len = 0;
+ }
+ case LMC_RAW: /* Setup the task queue, maybe we should notify someone? */
+ {
+ }
+ default:
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_attach out");
+}
+
+// detach
+void lmc_proto_detach(lmc_softc_t *sc) /*FOLD00*/
+{
+ switch(sc->if_type){
+ case LMC_PPP:
+ SPPP_detach(sc);
+ break;
+ case LMC_RAW: /* Tell someone we're detaching? */
+ break;
+ default:
+ break;
+ }
+
+}
+
+// reopen
+void lmc_proto_reopen(lmc_softc_t *sc) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_reopen in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ SPPP_reopen(sc);
+ break;
+ case LMC_RAW: /* Reset the interface after being down, prerape to receive packets again */
+ break;
+ default:
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_reopen out");
+}
+
+
+// ioctl
+int lmc_proto_ioctl(lmc_softc_t *sc, struct ifreq *ifr, int cmd) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_ioctl out");
+ switch(sc->if_type){
+ case LMC_PPP:
+ return SPPP_do_ioctl (sc, ifr, cmd);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_ioctl out");
+}
+
+// open
+void lmc_proto_open(lmc_softc_t *sc) /*FOLD00*/
+{
+ int ret;
+
+ lmc_trace(sc->lmc_device, "lmc_proto_open in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ ret = SPPP_open(sc);
+ if(ret < 0)
+ printk("%s: syncPPP open failed: %d\n", sc->name, ret);
+ break;
+ case LMC_RAW: /* We're about to start getting packets! */
+ break;
+ default:
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_open out");
+}
+
+// close
+
+void lmc_proto_close(lmc_softc_t *sc) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_close in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ SPPP_close(sc);
+ break;
+ case LMC_RAW: /* Interface going down */
+ break;
+ default:
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_close out");
+}
+
+unsigned short lmc_proto_type(lmc_softc_t *sc, struct sk_buff *skb) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_type in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ return htons(ETH_P_WAN_PPP);
+ break;
+ case LMC_NET:
+ return htons(ETH_P_802_2);
+ break;
+ case LMC_RAW: /* Packet type for skbuff kind of useless */
+ return htons(ETH_P_802_2);
+ break;
+ default:
+ printk(KERN_WARNING "%s: No protocol set for this interface, assuming 802.2 (which is wrong!!)\n", sc->name);
+ return htons(ETH_P_802_2);
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_tye out");
+
+}
+
+void lmc_proto_netif(lmc_softc_t *sc, struct sk_buff *skb) /*FOLD00*/
+{
+ lmc_trace(sc->lmc_device, "lmc_proto_netif in");
+ switch(sc->if_type){
+ case LMC_PPP:
+ case LMC_NET:
+ default:
+ skb->dev->last_rx = jiffies;
+ netif_rx(skb);
+ break;
+ case LMC_RAW:
+ break;
+ }
+ lmc_trace(sc->lmc_device, "lmc_proto_netif out");
+}
+
diff --git a/drivers/net/wan/lmc/lmc_proto.h b/drivers/net/wan/lmc/lmc_proto.h
new file mode 100644
index 00000000000..080a5577334
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_proto.h
@@ -0,0 +1,16 @@
+#ifndef _LMC_PROTO_H_
+#define _LMC_PROTO_H_
+
+void lmc_proto_init(lmc_softc_t *sc);
+void lmc_proto_attach(lmc_softc_t *sc);
+void lmc_proto_detach(lmc_softc_t *sc);
+void lmc_proto_reopen(lmc_softc_t *sc);
+int lmc_proto_ioctl(lmc_softc_t *sc, struct ifreq *ifr, int cmd);
+void lmc_proto_open(lmc_softc_t *sc);
+void lmc_proto_close(lmc_softc_t *sc);
+unsigned short lmc_proto_type(lmc_softc_t *sc, struct sk_buff *skb);
+void lmc_proto_netif(lmc_softc_t *sc, struct sk_buff *skb);
+int lmc_skb_rawpackets(char *buf, char **start, off_t offset, int len, int unused);
+
+#endif
+
diff --git a/drivers/net/wan/lmc/lmc_var.h b/drivers/net/wan/lmc/lmc_var.h
new file mode 100644
index 00000000000..6d003a39bfa
--- /dev/null
+++ b/drivers/net/wan/lmc/lmc_var.h
@@ -0,0 +1,570 @@
+#ifndef _LMC_VAR_H_
+#define _LMC_VAR_H_
+
+/* $Id: lmc_var.h,v 1.17 2000/04/06 12:16:47 asj Exp $ */
+
+ /*
+ * Copyright (c) 1997-2000 LAN Media Corporation (LMC)
+ * All rights reserved. www.lanmedia.com
+ *
+ * This code is written by:
+ * Andrew Stanley-Jones (asj@cban.com)
+ * Rob Braun (bbraun@vix.com),
+ * Michael Graff (explorer@vix.com) and
+ * Matt Thomas (matt@3am-software.com).
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License version 2, incorporated herein by reference.
+ */
+
+#include <linux/timer.h>
+
+#ifndef __KERNEL__
+typedef signed char s8;
+typedef unsigned char u8;
+
+typedef signed short s16;
+typedef unsigned short u16;
+
+typedef signed int s32;
+typedef unsigned int u32;
+
+typedef signed long long s64;
+typedef unsigned long long u64;
+
+#define BITS_PER_LONG 32
+
+#endif
+
+/*
+ * basic definitions used in lmc include files
+ */
+
+typedef struct lmc___softc lmc_softc_t;
+typedef struct lmc___media lmc_media_t;
+typedef struct lmc___ctl lmc_ctl_t;
+
+#define lmc_csrptr_t unsigned long
+#define u_int16_t u16
+#define u_int8_t u8
+#define tulip_uint32_t u32
+
+#define LMC_REG_RANGE 0x80
+
+#define LMC_PRINTF_FMT "%s"
+#define LMC_PRINTF_ARGS (sc->lmc_device->name)
+
+#define TX_TIMEOUT (2*HZ)
+
+#define LMC_TXDESCS 32
+#define LMC_RXDESCS 32
+
+#define LMC_LINK_UP 1
+#define LMC_LINK_DOWN 0
+
+/* These macros for generic read and write to and from the dec chip */
+#define LMC_CSR_READ(sc, csr) \
+ inl((sc)->lmc_csrs.csr)
+#define LMC_CSR_WRITE(sc, reg, val) \
+ outl((val), (sc)->lmc_csrs.reg)
+
+//#ifdef _LINUX_DELAY_H
+// #define SLOW_DOWN_IO udelay(2);
+// #undef __SLOW_DOWN_IO
+// #define __SLOW_DOWN_IO udelay(2);
+//#endif
+
+#define DELAY(n) SLOW_DOWN_IO
+
+#define lmc_delay() inl(sc->lmc_csrs.csr_9)
+
+/* This macro sync's up with the mii so that reads and writes can take place */
+#define LMC_MII_SYNC(sc) do {int n=32; while( n >= 0 ) { \
+ LMC_CSR_WRITE((sc), csr_9, 0x20000); \
+ lmc_delay(); \
+ LMC_CSR_WRITE((sc), csr_9, 0x30000); \
+ lmc_delay(); \
+ n--; }} while(0)
+
+struct lmc_regfile_t {
+ lmc_csrptr_t csr_busmode; /* CSR0 */
+ lmc_csrptr_t csr_txpoll; /* CSR1 */
+ lmc_csrptr_t csr_rxpoll; /* CSR2 */
+ lmc_csrptr_t csr_rxlist; /* CSR3 */
+ lmc_csrptr_t csr_txlist; /* CSR4 */
+ lmc_csrptr_t csr_status; /* CSR5 */
+ lmc_csrptr_t csr_command; /* CSR6 */
+ lmc_csrptr_t csr_intr; /* CSR7 */
+ lmc_csrptr_t csr_missed_frames; /* CSR8 */
+ lmc_csrptr_t csr_9; /* CSR9 */
+ lmc_csrptr_t csr_10; /* CSR10 */
+ lmc_csrptr_t csr_11; /* CSR11 */
+ lmc_csrptr_t csr_12; /* CSR12 */
+ lmc_csrptr_t csr_13; /* CSR13 */
+ lmc_csrptr_t csr_14; /* CSR14 */
+ lmc_csrptr_t csr_15; /* CSR15 */
+};
+
+#define csr_enetrom csr_9 /* 21040 */
+#define csr_reserved csr_10 /* 21040 */
+#define csr_full_duplex csr_11 /* 21040 */
+#define csr_bootrom csr_10 /* 21041/21140A/?? */
+#define csr_gp csr_12 /* 21140* */
+#define csr_watchdog csr_15 /* 21140* */
+#define csr_gp_timer csr_11 /* 21041/21140* */
+#define csr_srom_mii csr_9 /* 21041/21140* */
+#define csr_sia_status csr_12 /* 2104x */
+#define csr_sia_connectivity csr_13 /* 2104x */
+#define csr_sia_tx_rx csr_14 /* 2104x */
+#define csr_sia_general csr_15 /* 2104x */
+
+/* tulip length/control transmit descriptor definitions
+ * used to define bits in the second tulip_desc_t field (length)
+ * for the transmit descriptor -baz */
+
+#define LMC_TDES_FIRST_BUFFER_SIZE ((u_int32_t)(0x000007FF))
+#define LMC_TDES_SECOND_BUFFER_SIZE ((u_int32_t)(0x003FF800))
+#define LMC_TDES_HASH_FILTERING ((u_int32_t)(0x00400000))
+#define LMC_TDES_DISABLE_PADDING ((u_int32_t)(0x00800000))
+#define LMC_TDES_SECOND_ADDR_CHAINED ((u_int32_t)(0x01000000))
+#define LMC_TDES_END_OF_RING ((u_int32_t)(0x02000000))
+#define LMC_TDES_ADD_CRC_DISABLE ((u_int32_t)(0x04000000))
+#define LMC_TDES_SETUP_PACKET ((u_int32_t)(0x08000000))
+#define LMC_TDES_INVERSE_FILTERING ((u_int32_t)(0x10000000))
+#define LMC_TDES_FIRST_SEGMENT ((u_int32_t)(0x20000000))
+#define LMC_TDES_LAST_SEGMENT ((u_int32_t)(0x40000000))
+#define LMC_TDES_INTERRUPT_ON_COMPLETION ((u_int32_t)(0x80000000))
+
+#define TDES_SECOND_BUFFER_SIZE_BIT_NUMBER 11
+#define TDES_COLLISION_COUNT_BIT_NUMBER 3
+
+/* Constants for the RCV descriptor RDES */
+
+#define LMC_RDES_OVERFLOW ((u_int32_t)(0x00000001))
+#define LMC_RDES_CRC_ERROR ((u_int32_t)(0x00000002))
+#define LMC_RDES_DRIBBLING_BIT ((u_int32_t)(0x00000004))
+#define LMC_RDES_REPORT_ON_MII_ERR ((u_int32_t)(0x00000008))
+#define LMC_RDES_RCV_WATCHDOG_TIMEOUT ((u_int32_t)(0x00000010))
+#define LMC_RDES_FRAME_TYPE ((u_int32_t)(0x00000020))
+#define LMC_RDES_COLLISION_SEEN ((u_int32_t)(0x00000040))
+#define LMC_RDES_FRAME_TOO_LONG ((u_int32_t)(0x00000080))
+#define LMC_RDES_LAST_DESCRIPTOR ((u_int32_t)(0x00000100))
+#define LMC_RDES_FIRST_DESCRIPTOR ((u_int32_t)(0x00000200))
+#define LMC_RDES_MULTICAST_FRAME ((u_int32_t)(0x00000400))
+#define LMC_RDES_RUNT_FRAME ((u_int32_t)(0x00000800))
+#define LMC_RDES_DATA_TYPE ((u_int32_t)(0x00003000))
+#define LMC_RDES_LENGTH_ERROR ((u_int32_t)(0x00004000))
+#define LMC_RDES_ERROR_SUMMARY ((u_int32_t)(0x00008000))
+#define LMC_RDES_FRAME_LENGTH ((u_int32_t)(0x3FFF0000))
+#define LMC_RDES_OWN_BIT ((u_int32_t)(0x80000000))
+
+#define RDES_FRAME_LENGTH_BIT_NUMBER 16
+
+#define LMC_RDES_ERROR_MASK ( (u_int32_t)( \
+ LMC_RDES_OVERFLOW \
+ | LMC_RDES_DRIBBLING_BIT \
+ | LMC_RDES_REPORT_ON_MII_ERR \
+ | LMC_RDES_COLLISION_SEEN ) )
+
+
+/*
+ * Ioctl info
+ */
+
+typedef struct {
+ u_int32_t n;
+ u_int32_t m;
+ u_int32_t v;
+ u_int32_t x;
+ u_int32_t r;
+ u_int32_t f;
+ u_int32_t exact;
+} lmc_av9110_t;
+
+/*
+ * Common structure passed to the ioctl code.
+ */
+struct lmc___ctl {
+ u_int32_t cardtype;
+ u_int32_t clock_source; /* HSSI, T1 */
+ u_int32_t clock_rate; /* T1 */
+ u_int32_t crc_length;
+ u_int32_t cable_length; /* DS3 */
+ u_int32_t scrambler_onoff; /* DS3 */
+ u_int32_t cable_type; /* T1 */
+ u_int32_t keepalive_onoff; /* protocol */
+ u_int32_t ticks; /* ticks/sec */
+ union {
+ lmc_av9110_t ssi;
+ } cardspec;
+ u_int32_t circuit_type; /* T1 or E1 */
+};
+
+
+/*
+ * Carefull, look at the data sheet, there's more to this
+ * structure than meets the eye. It should probably be:
+ *
+ * struct tulip_desc_t {
+ * u8 own:1;
+ * u32 status:31;
+ * u32 control:10;
+ * u32 buffer1;
+ * u32 buffer2;
+ * };
+ * You could also expand status control to provide more bit information
+ */
+
+struct tulip_desc_t {
+ s32 status;
+ s32 length;
+ u32 buffer1;
+ u32 buffer2;
+};
+
+/*
+ * media independent methods to check on media status, link, light LEDs,
+ * etc.
+ */
+struct lmc___media {
+ void (* init)(lmc_softc_t * const);
+ void (* defaults)(lmc_softc_t * const);
+ void (* set_status)(lmc_softc_t * const, lmc_ctl_t *);
+ void (* set_clock_source)(lmc_softc_t * const, int);
+ void (* set_speed)(lmc_softc_t * const, lmc_ctl_t *);
+ void (* set_cable_length)(lmc_softc_t * const, int);
+ void (* set_scrambler)(lmc_softc_t * const, int);
+ int (* get_link_status)(lmc_softc_t * const);
+ void (* set_link_status)(lmc_softc_t * const, int);
+ void (* set_crc_length)(lmc_softc_t * const, int);
+ void (* set_circuit_type)(lmc_softc_t * const, int);
+ void (* watchdog)(lmc_softc_t * const);
+};
+
+
+#define STATCHECK 0xBEEFCAFE
+
+/* Included in this structure are first
+ * - standard net_device_stats
+ * - some other counters used for debug and driver performance
+ * evaluation -baz
+ */
+struct lmc_statistics
+{
+ unsigned long rx_packets; /* total packets received */
+ unsigned long tx_packets; /* total packets transmitted */
+ unsigned long rx_bytes;
+ unsigned long tx_bytes;
+
+ unsigned long rx_errors; /* bad packets received */
+ unsigned long tx_errors; /* packet transmit problems */
+ unsigned long rx_dropped; /* no space in linux buffers */
+ unsigned long tx_dropped; /* no space available in linux */
+ unsigned long multicast; /* multicast packets received */
+ unsigned long collisions;
+
+ /* detailed rx_errors: */
+ unsigned long rx_length_errors;
+ unsigned long rx_over_errors; /* receiver ring buff overflow */
+ unsigned long rx_crc_errors; /* recved pkt with crc error */
+ unsigned long rx_frame_errors; /* recv'd frame alignment error */
+ unsigned long rx_fifo_errors; /* recv'r fifo overrun */
+ unsigned long rx_missed_errors; /* receiver missed packet */
+
+ /* detailed tx_errors */
+ unsigned long tx_aborted_errors;
+ unsigned long tx_carrier_errors;
+ unsigned long tx_fifo_errors;
+ unsigned long tx_heartbeat_errors;
+ unsigned long tx_window_errors;
+
+ /* for cslip etc */
+ unsigned long rx_compressed;
+ unsigned long tx_compressed;
+
+ /* -------------------------------------
+ * Custom stats & counters follow -baz */
+ u_int32_t version_size;
+ u_int32_t lmc_cardtype;
+
+ u_int32_t tx_ProcTimeout;
+ u_int32_t tx_IntTimeout;
+ u_int32_t tx_NoCompleteCnt;
+ u_int32_t tx_MaxXmtsB4Int;
+ u_int32_t tx_TimeoutCnt;
+ u_int32_t tx_OutOfSyncPtr;
+ u_int32_t tx_tbusy0;
+ u_int32_t tx_tbusy1;
+ u_int32_t tx_tbusy_calls;
+ u_int32_t resetCount;
+ u_int32_t lmc_txfull;
+ u_int32_t tbusy;
+ u_int32_t dirtyTx;
+ u_int32_t lmc_next_tx;
+ u_int32_t otherTypeCnt;
+ u_int32_t lastType;
+ u_int32_t lastTypeOK;
+ u_int32_t txLoopCnt;
+ u_int32_t usedXmtDescripCnt;
+ u_int32_t txIndexCnt;
+ u_int32_t rxIntLoopCnt;
+
+ u_int32_t rx_SmallPktCnt;
+ u_int32_t rx_BadPktSurgeCnt;
+ u_int32_t rx_BuffAllocErr;
+ u_int32_t tx_lossOfClockCnt;
+
+ /* T1 error counters */
+ u_int32_t framingBitErrorCount;
+ u_int32_t lineCodeViolationCount;
+
+ u_int32_t lossOfFrameCount;
+ u_int32_t changeOfFrameAlignmentCount;
+ u_int32_t severelyErroredFrameCount;
+
+ u_int32_t check;
+};
+
+
+typedef struct lmc_xinfo {
+ u_int32_t Magic0; /* BEEFCAFE */
+
+ u_int32_t PciCardType;
+ u_int32_t PciSlotNumber; /* PCI slot number */
+
+ u_int16_t DriverMajorVersion;
+ u_int16_t DriverMinorVersion;
+ u_int16_t DriverSubVersion;
+
+ u_int16_t XilinxRevisionNumber;
+ u_int16_t MaxFrameSize;
+
+ u_int16_t t1_alarm1_status;
+ u_int16_t t1_alarm2_status;
+
+ int link_status;
+ u_int32_t mii_reg16;
+
+ u_int32_t Magic1; /* DEADBEEF */
+} LMC_XINFO;
+
+
+/*
+ * forward decl
+ */
+struct lmc___softc {
+ void *if_ptr; /* General purpose pointer (used by SPPP) */
+ char *name;
+ u8 board_idx;
+ struct lmc_statistics stats;
+ struct net_device *lmc_device;
+
+ int hang, rxdesc, bad_packet, some_counter;
+ u_int32_t txgo;
+ struct lmc_regfile_t lmc_csrs;
+ volatile u_int32_t lmc_txtick;
+ volatile u_int32_t lmc_rxtick;
+ u_int32_t lmc_flags;
+ u_int32_t lmc_intrmask; /* our copy of csr_intr */
+ u_int32_t lmc_cmdmode; /* our copy of csr_cmdmode */
+ u_int32_t lmc_busmode; /* our copy of csr_busmode */
+ u_int32_t lmc_gpio_io; /* state of in/out settings */
+ u_int32_t lmc_gpio; /* state of outputs */
+ struct sk_buff* lmc_txq[LMC_TXDESCS];
+ struct sk_buff* lmc_rxq[LMC_RXDESCS];
+ volatile
+ struct tulip_desc_t lmc_rxring[LMC_RXDESCS];
+ volatile
+ struct tulip_desc_t lmc_txring[LMC_TXDESCS];
+ unsigned int lmc_next_rx, lmc_next_tx;
+ volatile
+ unsigned int lmc_taint_tx, lmc_taint_rx;
+ int lmc_tx_start, lmc_txfull;
+ int lmc_txbusy;
+ u_int16_t lmc_miireg16;
+ int lmc_ok;
+ int last_link_status;
+ int lmc_cardtype;
+ u_int32_t last_frameerr;
+ lmc_media_t *lmc_media;
+ struct timer_list timer;
+ lmc_ctl_t ictl;
+ u_int32_t TxDescriptControlInit;
+
+ int tx_TimeoutInd; /* additional driver state */
+ int tx_TimeoutDisplay;
+ unsigned int lastlmc_taint_tx;
+ int lasttx_packets;
+ u_int32_t tx_clockState;
+ u_int32_t lmc_crcSize;
+ LMC_XINFO lmc_xinfo;
+ char lmc_yel, lmc_blue, lmc_red; /* for T1 and DS3 */
+ char lmc_timing; /* for HSSI and SSI */
+ int got_irq;
+
+ char last_led_err[4];
+
+ u32 last_int;
+ u32 num_int;
+
+ spinlock_t lmc_lock;
+ u_int16_t if_type; /* PPP or NET */
+ struct ppp_device *pd;
+
+ /* Failure cases */
+ u8 failed_ring;
+ u8 failed_recv_alloc;
+
+ /* Structure check */
+ u32 check;
+};
+
+#define LMC_PCI_TIME 1
+#define LMC_EXT_TIME 0
+
+#define PKT_BUF_SZ 1542 /* was 1536 */
+
+/* CSR5 settings */
+#define TIMER_INT 0x00000800
+#define TP_LINK_FAIL 0x00001000
+#define TP_LINK_PASS 0x00000010
+#define NORMAL_INT 0x00010000
+#define ABNORMAL_INT 0x00008000
+#define RX_JABBER_INT 0x00000200
+#define RX_DIED 0x00000100
+#define RX_NOBUFF 0x00000080
+#define RX_INT 0x00000040
+#define TX_FIFO_UNDER 0x00000020
+#define TX_JABBER 0x00000008
+#define TX_NOBUFF 0x00000004
+#define TX_DIED 0x00000002
+#define TX_INT 0x00000001
+
+/* CSR6 settings */
+#define OPERATION_MODE 0x00000200 /* Full Duplex */
+#define PROMISC_MODE 0x00000040 /* Promiscuous Mode */
+#define RECIEVE_ALL 0x40000000 /* Recieve All */
+#define PASS_BAD_FRAMES 0x00000008 /* Pass Bad Frames */
+
+/* Dec control registers CSR6 as well */
+#define LMC_DEC_ST 0x00002000
+#define LMC_DEC_SR 0x00000002
+
+/* CSR15 settings */
+#define RECV_WATCHDOG_DISABLE 0x00000010
+#define JABBER_DISABLE 0x00000001
+
+/* More settings */
+/*
+ * aSR6 -- Command (Operation Mode) Register
+ */
+#define TULIP_CMD_RECEIVEALL 0x40000000L /* (RW) Receivel all frames? */
+#define TULIP_CMD_MUSTBEONE 0x02000000L /* (RW) Must Be One (21140) */
+#define TULIP_CMD_TXTHRSHLDCTL 0x00400000L /* (RW) Transmit Threshold Mode (21140) */
+#define TULIP_CMD_STOREFWD 0x00200000L /* (RW) Store and Foward (21140) */
+#define TULIP_CMD_NOHEARTBEAT 0x00080000L /* (RW) No Heartbeat (21140) */
+#define TULIP_CMD_PORTSELECT 0x00040000L /* (RW) Post Select (100Mb) (21140) */
+#define TULIP_CMD_FULLDUPLEX 0x00000200L /* (RW) Full Duplex Mode */
+#define TULIP_CMD_OPERMODE 0x00000C00L /* (RW) Operating Mode */
+#define TULIP_CMD_PROMISCUOUS 0x00000041L /* (RW) Promiscuous Mode */
+#define TULIP_CMD_PASSBADPKT 0x00000008L /* (RW) Pass Bad Frames */
+#define TULIP_CMD_THRESHOLDCTL 0x0000C000L /* (RW) Threshold Control */
+
+#define TULIP_GP_PINSET 0x00000100L
+#define TULIP_BUSMODE_SWRESET 0x00000001L
+#define TULIP_WATCHDOG_TXDISABLE 0x00000001L
+#define TULIP_WATCHDOG_RXDISABLE 0x00000010L
+
+#define TULIP_STS_NORMALINTR 0x00010000L /* (RW) Normal Interrupt */
+#define TULIP_STS_ABNRMLINTR 0x00008000L /* (RW) Abnormal Interrupt */
+#define TULIP_STS_ERI 0x00004000L /* (RW) Early Receive Interrupt */
+#define TULIP_STS_SYSERROR 0x00002000L /* (RW) System Error */
+#define TULIP_STS_GTE 0x00000800L /* (RW) General Pupose Timer Exp */
+#define TULIP_STS_ETI 0x00000400L /* (RW) Early Transmit Interrupt */
+#define TULIP_STS_RXWT 0x00000200L /* (RW) Receiver Watchdog Timeout */
+#define TULIP_STS_RXSTOPPED 0x00000100L /* (RW) Receiver Process Stopped */
+#define TULIP_STS_RXNOBUF 0x00000080L /* (RW) Receive Buf Unavail */
+#define TULIP_STS_RXINTR 0x00000040L /* (RW) Receive Interrupt */
+#define TULIP_STS_TXUNDERFLOW 0x00000020L /* (RW) Transmit Underflow */
+#define TULIP_STS_TXJABER 0x00000008L /* (RW) Jabber timeout */
+#define TULIP_STS_TXNOBUF 0x00000004L
+#define TULIP_STS_TXSTOPPED 0x00000002L /* (RW) Transmit Process Stopped */
+#define TULIP_STS_TXINTR 0x00000001L /* (RW) Transmit Interrupt */
+
+#define TULIP_STS_RXS_STOPPED 0x00000000L /* 000 - Stopped */
+
+#define TULIP_STS_RXSTOPPED 0x00000100L /* (RW) Receive Process Stopped */
+#define TULIP_STS_RXNOBUF 0x00000080L
+
+#define TULIP_CMD_TXRUN 0x00002000L /* (RW) Start/Stop Transmitter */
+#define TULIP_CMD_RXRUN 0x00000002L /* (RW) Start/Stop Receive Filtering */
+#define TULIP_DSTS_TxDEFERRED 0x00000001 /* Initially Deferred */
+#define TULIP_DSTS_OWNER 0x80000000 /* Owner (1 = 21040) */
+#define TULIP_DSTS_RxMIIERR 0x00000008
+#define LMC_DSTS_ERRSUM (TULIP_DSTS_RxMIIERR)
+
+#define TULIP_DEFAULT_INTR_MASK (TULIP_STS_NORMALINTR \
+ | TULIP_STS_RXINTR \
+ | TULIP_STS_TXINTR \
+ | TULIP_STS_ABNRMLINTR \
+ | TULIP_STS_SYSERROR \
+ | TULIP_STS_TXSTOPPED \
+ | TULIP_STS_TXUNDERFLOW\
+ | TULIP_STS_RXSTOPPED )
+
+#define DESC_OWNED_BY_SYSTEM ((u_int32_t)(0x00000000))
+#define DESC_OWNED_BY_DC21X4 ((u_int32_t)(0x80000000))
+
+#ifndef TULIP_CMD_RECEIVEALL
+#define TULIP_CMD_RECEIVEALL 0x40000000L
+#endif
+
+/* Adapter module number */
+#define LMC_ADAP_HSSI 2
+#define LMC_ADAP_DS3 3
+#define LMC_ADAP_SSI 4
+#define LMC_ADAP_T1 5
+
+#define HDLC_HDR_LEN 4
+#define HDLC_ADDR_LEN 1
+#define HDLC_SLARP 0x8035
+#define LMC_MTU 1500
+#define SLARP_LINECHECK 2
+
+#define LMC_CRC_LEN_16 2 /* 16-bit CRC */
+#define LMC_CRC_LEN_32 4
+
+#ifdef LMC_HDLC
+/* definition of an hdlc header. */
+struct hdlc_hdr
+{
+ u8 address;
+ u8 control;
+ u16 type;
+};
+
+/* definition of a slarp header. */
+struct slarp
+{
+ long code;
+ union sl
+ {
+ struct
+ {
+ ulong address;
+ ulong mask;
+ ushort unused;
+ } add;
+ struct
+ {
+ ulong mysequence;
+ ulong yoursequence;
+ ushort reliability;
+ ulong time;
+ } chk;
+ } t;
+};
+#endif /* LMC_HDLC */
+
+
+#endif /* _LMC_VAR_H_ */
diff --git a/drivers/net/wan/n2.c b/drivers/net/wan/n2.c
new file mode 100644
index 00000000000..cd32751b64e
--- /dev/null
+++ b/drivers/net/wan/n2.c
@@ -0,0 +1,562 @@
+/*
+ * SDL Inc. RISCom/N2 synchronous serial card driver for Linux
+ *
+ * Copyright (C) 1998-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * For information see http://hq.pm.waw.pl/hdlc/
+ *
+ * Note: integrated CSU/DSU/DDS are not supported by this driver
+ *
+ * Sources of information:
+ * Hitachi HD64570 SCA User's Manual
+ * SDL Inc. PPP/HDLC/CISCO driver
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <asm/io.h>
+#include "hd64570.h"
+
+
+static const char* version = "SDL RISCom/N2 driver version: 1.15";
+static const char* devname = "RISCom/N2";
+
+#undef DEBUG_PKT
+#define DEBUG_RINGS
+
+#define USE_WINDOWSIZE 16384
+#define USE_BUS16BITS 1
+#define CLOCK_BASE 9830400 /* 9.8304 MHz */
+#define MAX_PAGES 16 /* 16 RAM pages at max */
+#define MAX_RAM_SIZE 0x80000 /* 512 KB */
+#if MAX_RAM_SIZE > MAX_PAGES * USE_WINDOWSIZE
+#undef MAX_RAM_SIZE
+#define MAX_RAM_SIZE (MAX_PAGES * USE_WINDOWSIZE)
+#endif
+#define N2_IOPORTS 0x10
+#define NEED_DETECT_RAM
+#define NEED_SCA_MSCI_INTR
+#define MAX_TX_BUFFERS 10
+
+static char *hw = NULL; /* pointer to hw=xxx command line string */
+
+/* RISCom/N2 Board Registers */
+
+/* PC Control Register */
+#define N2_PCR 0
+#define PCR_RUNSCA 1 /* Run 64570 */
+#define PCR_VPM 2 /* Enable VPM - needed if using RAM above 1 MB */
+#define PCR_ENWIN 4 /* Open window */
+#define PCR_BUS16 8 /* 16-bit bus */
+
+
+/* Memory Base Address Register */
+#define N2_BAR 2
+
+
+/* Page Scan Register */
+#define N2_PSR 4
+#define WIN16K 0x00
+#define WIN32K 0x20
+#define WIN64K 0x40
+#define PSR_WINBITS 0x60
+#define PSR_DMAEN 0x80
+#define PSR_PAGEBITS 0x0F
+
+
+/* Modem Control Reg */
+#define N2_MCR 6
+#define CLOCK_OUT_PORT1 0x80
+#define CLOCK_OUT_PORT0 0x40
+#define TX422_PORT1 0x20
+#define TX422_PORT0 0x10
+#define DSR_PORT1 0x08
+#define DSR_PORT0 0x04
+#define DTR_PORT1 0x02
+#define DTR_PORT0 0x01
+
+
+typedef struct port_s {
+ struct net_device *dev;
+ struct card_s *card;
+ spinlock_t lock; /* TX lock */
+ sync_serial_settings settings;
+ int valid; /* port enabled */
+ int rxpart; /* partial frame received, next frame invalid*/
+ unsigned short encoding;
+ unsigned short parity;
+ u16 rxin; /* rx ring buffer 'in' pointer */
+ u16 txin; /* tx ring buffer 'in' and 'last' pointers */
+ u16 txlast;
+ u8 rxs, txs, tmc; /* SCA registers */
+ u8 phy_node; /* physical port # - 0 or 1 */
+ u8 log_node; /* logical port # */
+}port_t;
+
+
+
+typedef struct card_s {
+ u8 __iomem *winbase; /* ISA window base address */
+ u32 phy_winbase; /* ISA physical base address */
+ u32 ram_size; /* number of bytes */
+ u16 io; /* IO Base address */
+ u16 buff_offset; /* offset of first buffer of first channel */
+ u16 rx_ring_buffers; /* number of buffers in a ring */
+ u16 tx_ring_buffers;
+ u8 irq; /* IRQ (3-15) */
+
+ port_t ports[2];
+ struct card_s *next_card;
+}card_t;
+
+
+static card_t *first_card;
+static card_t **new_card = &first_card;
+
+
+#define sca_reg(reg, card) (0x8000 | (card)->io | \
+ ((reg) & 0x0F) | (((reg) & 0xF0) << 6))
+#define sca_in(reg, card) inb(sca_reg(reg, card))
+#define sca_out(value, reg, card) outb(value, sca_reg(reg, card))
+#define sca_inw(reg, card) inw(sca_reg(reg, card))
+#define sca_outw(value, reg, card) outw(value, sca_reg(reg, card))
+
+#define port_to_card(port) ((port)->card)
+#define log_node(port) ((port)->log_node)
+#define phy_node(port) ((port)->phy_node)
+#define winsize(card) (USE_WINDOWSIZE)
+#define winbase(card) ((card)->winbase)
+#define get_port(card, port) ((card)->ports[port].valid ? \
+ &(card)->ports[port] : NULL)
+
+
+
+static __inline__ u8 sca_get_page(card_t *card)
+{
+ return inb(card->io + N2_PSR) & PSR_PAGEBITS;
+}
+
+
+static __inline__ void openwin(card_t *card, u8 page)
+{
+ u8 psr = inb(card->io + N2_PSR);
+ outb((psr & ~PSR_PAGEBITS) | page, card->io + N2_PSR);
+}
+
+
+
+#include "hd6457x.c"
+
+
+
+static void n2_set_iface(port_t *port)
+{
+ card_t *card = port->card;
+ int io = card->io;
+ u8 mcr = inb(io + N2_MCR);
+ u8 msci = get_msci(port);
+ u8 rxs = port->rxs & CLK_BRG_MASK;
+ u8 txs = port->txs & CLK_BRG_MASK;
+
+ switch(port->settings.clock_type) {
+ case CLOCK_INT:
+ mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
+ rxs |= CLK_BRG_RX; /* BRG output */
+ txs |= CLK_RXCLK_TX; /* RX clock */
+ break;
+
+ case CLOCK_TXINT:
+ mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
+ rxs |= CLK_LINE_RX; /* RXC input */
+ txs |= CLK_BRG_TX; /* BRG output */
+ break;
+
+ case CLOCK_TXFROMRX:
+ mcr |= port->phy_node ? CLOCK_OUT_PORT1 : CLOCK_OUT_PORT0;
+ rxs |= CLK_LINE_RX; /* RXC input */
+ txs |= CLK_RXCLK_TX; /* RX clock */
+ break;
+
+ default: /* Clock EXTernal */
+ mcr &= port->phy_node ? ~CLOCK_OUT_PORT1 : ~CLOCK_OUT_PORT0;
+ rxs |= CLK_LINE_RX; /* RXC input */
+ txs |= CLK_LINE_TX; /* TXC input */
+ }
+
+ outb(mcr, io + N2_MCR);
+ port->rxs = rxs;
+ port->txs = txs;
+ sca_out(rxs, msci + RXS, card);
+ sca_out(txs, msci + TXS, card);
+ sca_set_port(port);
+}
+
+
+
+static int n2_open(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ int io = port->card->io;
+ u8 mcr = inb(io + N2_MCR) | (port->phy_node ? TX422_PORT1:TX422_PORT0);
+ int result;
+
+ result = hdlc_open(dev);
+ if (result)
+ return result;
+
+ mcr &= port->phy_node ? ~DTR_PORT1 : ~DTR_PORT0; /* set DTR ON */
+ outb(mcr, io + N2_MCR);
+
+ outb(inb(io + N2_PCR) | PCR_ENWIN, io + N2_PCR); /* open window */
+ outb(inb(io + N2_PSR) | PSR_DMAEN, io + N2_PSR); /* enable dma */
+ sca_open(dev);
+ n2_set_iface(port);
+ return 0;
+}
+
+
+
+static int n2_close(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ int io = port->card->io;
+ u8 mcr = inb(io+N2_MCR) | (port->phy_node ? TX422_PORT1 : TX422_PORT0);
+
+ sca_close(dev);
+ mcr |= port->phy_node ? DTR_PORT1 : DTR_PORT0; /* set DTR OFF */
+ outb(mcr, io + N2_MCR);
+ hdlc_close(dev);
+ return 0;
+}
+
+
+
+static int n2_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings new_line;
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ port_t *port = dev_to_port(dev);
+
+#ifdef DEBUG_RINGS
+ if (cmd == SIOCDEVPRIVATE) {
+ sca_dump_rings(dev);
+ return 0;
+ }
+#endif
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(line, &port->settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_SYNC_SERIAL:
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&new_line, line, size))
+ return -EFAULT;
+
+ if (new_line.clock_type != CLOCK_EXT &&
+ new_line.clock_type != CLOCK_TXFROMRX &&
+ new_line.clock_type != CLOCK_INT &&
+ new_line.clock_type != CLOCK_TXINT)
+ return -EINVAL; /* No such clock setting */
+
+ if (new_line.loopback != 0 && new_line.loopback != 1)
+ return -EINVAL;
+
+ memcpy(&port->settings, &new_line, size); /* Update settings */
+ n2_set_iface(port);
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+
+
+static void n2_destroy_card(card_t *card)
+{
+ int cnt;
+
+ for (cnt = 0; cnt < 2; cnt++)
+ if (card->ports[cnt].card) {
+ struct net_device *dev = port_to_dev(&card->ports[cnt]);
+ unregister_hdlc_device(dev);
+ }
+
+ if (card->irq)
+ free_irq(card->irq, card);
+
+ if (card->winbase) {
+ iounmap(card->winbase);
+ release_mem_region(card->phy_winbase, USE_WINDOWSIZE);
+ }
+
+ if (card->io)
+ release_region(card->io, N2_IOPORTS);
+ if (card->ports[0].dev)
+ free_netdev(card->ports[0].dev);
+ if (card->ports[1].dev)
+ free_netdev(card->ports[1].dev);
+ kfree(card);
+}
+
+
+
+static int __init n2_run(unsigned long io, unsigned long irq,
+ unsigned long winbase, long valid0, long valid1)
+{
+ card_t *card;
+ u8 cnt, pcr;
+ int i;
+
+ if (io < 0x200 || io > 0x3FF || (io % N2_IOPORTS) != 0) {
+ printk(KERN_ERR "n2: invalid I/O port value\n");
+ return -ENODEV;
+ }
+
+ if (irq < 3 || irq > 15 || irq == 6) /* FIXME */ {
+ printk(KERN_ERR "n2: invalid IRQ value\n");
+ return -ENODEV;
+ }
+
+ if (winbase < 0xA0000 || winbase > 0xFFFFF || (winbase & 0xFFF) != 0) {
+ printk(KERN_ERR "n2: invalid RAM value\n");
+ return -ENODEV;
+ }
+
+ card = kmalloc(sizeof(card_t), GFP_KERNEL);
+ if (card == NULL) {
+ printk(KERN_ERR "n2: unable to allocate memory\n");
+ return -ENOBUFS;
+ }
+ memset(card, 0, sizeof(card_t));
+
+ card->ports[0].dev = alloc_hdlcdev(&card->ports[0]);
+ card->ports[1].dev = alloc_hdlcdev(&card->ports[1]);
+ if (!card->ports[0].dev || !card->ports[1].dev) {
+ printk(KERN_ERR "n2: unable to allocate memory\n");
+ n2_destroy_card(card);
+ return -ENOMEM;
+ }
+
+ if (!request_region(io, N2_IOPORTS, devname)) {
+ printk(KERN_ERR "n2: I/O port region in use\n");
+ n2_destroy_card(card);
+ return -EBUSY;
+ }
+ card->io = io;
+
+ if (request_irq(irq, &sca_intr, 0, devname, card)) {
+ printk(KERN_ERR "n2: could not allocate IRQ\n");
+ n2_destroy_card(card);
+ return(-EBUSY);
+ }
+ card->irq = irq;
+
+ if (!request_mem_region(winbase, USE_WINDOWSIZE, devname)) {
+ printk(KERN_ERR "n2: could not request RAM window\n");
+ n2_destroy_card(card);
+ return(-EBUSY);
+ }
+ card->phy_winbase = winbase;
+ card->winbase = ioremap(winbase, USE_WINDOWSIZE);
+
+ outb(0, io + N2_PCR);
+ outb(winbase >> 12, io + N2_BAR);
+
+ switch (USE_WINDOWSIZE) {
+ case 16384:
+ outb(WIN16K, io + N2_PSR);
+ break;
+
+ case 32768:
+ outb(WIN32K, io + N2_PSR);
+ break;
+
+ case 65536:
+ outb(WIN64K, io + N2_PSR);
+ break;
+
+ default:
+ printk(KERN_ERR "n2: invalid window size\n");
+ n2_destroy_card(card);
+ return -ENODEV;
+ }
+
+ pcr = PCR_ENWIN | PCR_VPM | (USE_BUS16BITS ? PCR_BUS16 : 0);
+ outb(pcr, io + N2_PCR);
+
+ card->ram_size = sca_detect_ram(card, card->winbase, MAX_RAM_SIZE);
+
+ /* number of TX + RX buffers for one port */
+ i = card->ram_size / ((valid0 + valid1) * (sizeof(pkt_desc) +
+ HDLC_MAX_MRU));
+
+ card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
+ card->rx_ring_buffers = i - card->tx_ring_buffers;
+
+ card->buff_offset = (valid0 + valid1) * sizeof(pkt_desc) *
+ (card->tx_ring_buffers + card->rx_ring_buffers);
+
+ printk(KERN_INFO "n2: RISCom/N2 %u KB RAM, IRQ%u, "
+ "using %u TX + %u RX packets rings\n", card->ram_size / 1024,
+ card->irq, card->tx_ring_buffers, card->rx_ring_buffers);
+
+ if (card->tx_ring_buffers < 1) {
+ printk(KERN_ERR "n2: RAM test failed\n");
+ n2_destroy_card(card);
+ return -EIO;
+ }
+
+ pcr |= PCR_RUNSCA; /* run SCA */
+ outb(pcr, io + N2_PCR);
+ outb(0, io + N2_MCR);
+
+ sca_init(card, 0);
+ for (cnt = 0; cnt < 2; cnt++) {
+ port_t *port = &card->ports[cnt];
+ struct net_device *dev = port_to_dev(port);
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ if ((cnt == 0 && !valid0) || (cnt == 1 && !valid1))
+ continue;
+
+ port->phy_node = cnt;
+ port->valid = 1;
+
+ if ((cnt == 1) && valid0)
+ port->log_node = 1;
+
+ spin_lock_init(&port->lock);
+ SET_MODULE_OWNER(dev);
+ dev->irq = irq;
+ dev->mem_start = winbase;
+ dev->mem_end = winbase + USE_WINDOWSIZE - 1;
+ dev->tx_queue_len = 50;
+ dev->do_ioctl = n2_ioctl;
+ dev->open = n2_open;
+ dev->stop = n2_close;
+ hdlc->attach = sca_attach;
+ hdlc->xmit = sca_xmit;
+ port->settings.clock_type = CLOCK_EXT;
+ port->card = card;
+
+ if (register_hdlc_device(dev)) {
+ printk(KERN_WARNING "n2: unable to register hdlc "
+ "device\n");
+ port->card = NULL;
+ n2_destroy_card(card);
+ return -ENOBUFS;
+ }
+ sca_init_sync_port(port); /* Set up SCA memory */
+
+ printk(KERN_INFO "%s: RISCom/N2 node %d\n",
+ dev->name, port->phy_node);
+ }
+
+ *new_card = card;
+ new_card = &card->next_card;
+
+ return 0;
+}
+
+
+
+static int __init n2_init(void)
+{
+ if (hw==NULL) {
+#ifdef MODULE
+ printk(KERN_INFO "n2: no card initialized\n");
+#endif
+ return -ENOSYS; /* no parameters specified, abort */
+ }
+
+ printk(KERN_INFO "%s\n", version);
+
+ do {
+ unsigned long io, irq, ram;
+ long valid[2] = { 0, 0 }; /* Default = both ports disabled */
+
+ io = simple_strtoul(hw, &hw, 0);
+
+ if (*hw++ != ',')
+ break;
+ irq = simple_strtoul(hw, &hw, 0);
+
+ if (*hw++ != ',')
+ break;
+ ram = simple_strtoul(hw, &hw, 0);
+
+ if (*hw++ != ',')
+ break;
+ while(1) {
+ if (*hw == '0' && !valid[0])
+ valid[0] = 1; /* Port 0 enabled */
+ else if (*hw == '1' && !valid[1])
+ valid[1] = 1; /* Port 1 enabled */
+ else
+ break;
+ hw++;
+ }
+
+ if (!valid[0] && !valid[1])
+ break; /* at least one port must be used */
+
+ if (*hw == ':' || *hw == '\x0')
+ n2_run(io, irq, ram, valid[0], valid[1]);
+
+ if (*hw == '\x0')
+ return first_card ? 0 : -ENOSYS;
+ }while(*hw++ == ':');
+
+ printk(KERN_ERR "n2: invalid hardware parameters\n");
+ return first_card ? 0 : -ENOSYS;
+}
+
+
+static void __exit n2_cleanup(void)
+{
+ card_t *card = first_card;
+
+ while (card) {
+ card_t *ptr = card;
+ card = card->next_card;
+ n2_destroy_card(ptr);
+ }
+}
+
+
+module_init(n2_init);
+module_exit(n2_cleanup);
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("RISCom/N2 serial port driver");
+MODULE_LICENSE("GPL v2");
+module_param(hw, charp, 0444); /* hw=io,irq,ram,ports:io,irq,... */
diff --git a/drivers/net/wan/pc300-falc-lh.h b/drivers/net/wan/pc300-falc-lh.h
new file mode 100644
index 00000000000..01ed23ca76c
--- /dev/null
+++ b/drivers/net/wan/pc300-falc-lh.h
@@ -0,0 +1,1238 @@
+/*
+ * falc.h Description of the Siemens FALC T1/E1 framer.
+ *
+ * Author: Ivan Passos <ivan@cyclades.com>
+ *
+ * Copyright: (c) 2000-2001 Cyclades Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Log: falc-lh.h,v $
+ * Revision 3.1 2001/06/15 12:41:10 regina
+ * upping major version number
+ *
+ * Revision 1.1.1.1 2001/06/13 20:24:47 daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 1.1 2000/05/15 ivan
+ * Included DJA bits for the LIM2 register.
+ *
+ * Revision 1.0 2000/02/22 ivan
+ * Initial version.
+ *
+ */
+
+#ifndef _FALC_LH_H
+#define _FALC_LH_H
+
+#define NUM_OF_T1_CHANNELS 24
+#define NUM_OF_E1_CHANNELS 32
+
+/*>>>>>>>>>>>>>>>>> FALC Register Bits (Transmit Mode) <<<<<<<<<<<<<<<<<<< */
+
+/* CMDR (Command Register)
+ ---------------- E1 & T1 ------------------------------ */
+#define CMDR_RMC 0x80
+#define CMDR_RRES 0x40
+#define CMDR_XREP 0x20
+#define CMDR_XRES 0x10
+#define CMDR_XHF 0x08
+#define CMDR_XTF 0x04
+#define CMDR_XME 0x02
+#define CMDR_SRES 0x01
+
+/* MODE (Mode Register)
+ ----------------- E1 & T1 ----------------------------- */
+#define MODE_MDS2 0x80
+#define MODE_MDS1 0x40
+#define MODE_MDS0 0x20
+#define MODE_BRAC 0x10
+#define MODE_HRAC 0x08
+
+/* IPC (Interrupt Port Configuration)
+ ----------------- E1 & T1 ----------------------------- */
+#define IPC_VIS 0x80
+#define IPC_SCI 0x04
+#define IPC_IC1 0x02
+#define IPC_IC0 0x01
+
+/* CCR1 (Common Configuration Register 1)
+ ----------------- E1 & T1 ----------------------------- */
+#define CCR1_SFLG 0x80
+#define CCR1_XTS16RA 0x40
+#define CCR1_BRM 0x40
+#define CCR1_CASSYM 0x20
+#define CCR1_EDLX 0x20
+#define CCR1_EITS 0x10
+#define CCR1_ITF 0x08
+#define CCR1_RFT1 0x02
+#define CCR1_RFT0 0x01
+
+/* CCR3 (Common Configuration Register 3)
+ ---------------- E1 & T1 ------------------------------ */
+
+#define CCR3_PRE1 0x80
+#define CCR3_PRE0 0x40
+#define CCR3_EPT 0x20
+#define CCR3_RADD 0x10
+#define CCR3_RCRC 0x04
+#define CCR3_XCRC 0x02
+
+
+/* RTR1-4 (Receive Timeslot Register 1-4)
+ ---------------- E1 & T1 ------------------------------ */
+
+#define RTR1_TS0 0x80
+#define RTR1_TS1 0x40
+#define RTR1_TS2 0x20
+#define RTR1_TS3 0x10
+#define RTR1_TS4 0x08
+#define RTR1_TS5 0x04
+#define RTR1_TS6 0x02
+#define RTR1_TS7 0x01
+
+#define RTR2_TS8 0x80
+#define RTR2_TS9 0x40
+#define RTR2_TS10 0x20
+#define RTR2_TS11 0x10
+#define RTR2_TS12 0x08
+#define RTR2_TS13 0x04
+#define RTR2_TS14 0x02
+#define RTR2_TS15 0x01
+
+#define RTR3_TS16 0x80
+#define RTR3_TS17 0x40
+#define RTR3_TS18 0x20
+#define RTR3_TS19 0x10
+#define RTR3_TS20 0x08
+#define RTR3_TS21 0x04
+#define RTR3_TS22 0x02
+#define RTR3_TS23 0x01
+
+#define RTR4_TS24 0x80
+#define RTR4_TS25 0x40
+#define RTR4_TS26 0x20
+#define RTR4_TS27 0x10
+#define RTR4_TS28 0x08
+#define RTR4_TS29 0x04
+#define RTR4_TS30 0x02
+#define RTR4_TS31 0x01
+
+
+/* TTR1-4 (Transmit Timeslot Register 1-4)
+ ---------------- E1 & T1 ------------------------------ */
+
+#define TTR1_TS0 0x80
+#define TTR1_TS1 0x40
+#define TTR1_TS2 0x20
+#define TTR1_TS3 0x10
+#define TTR1_TS4 0x08
+#define TTR1_TS5 0x04
+#define TTR1_TS6 0x02
+#define TTR1_TS7 0x01
+
+#define TTR2_TS8 0x80
+#define TTR2_TS9 0x40
+#define TTR2_TS10 0x20
+#define TTR2_TS11 0x10
+#define TTR2_TS12 0x08
+#define TTR2_TS13 0x04
+#define TTR2_TS14 0x02
+#define TTR2_TS15 0x01
+
+#define TTR3_TS16 0x80
+#define TTR3_TS17 0x40
+#define TTR3_TS18 0x20
+#define TTR3_TS19 0x10
+#define TTR3_TS20 0x08
+#define TTR3_TS21 0x04
+#define TTR3_TS22 0x02
+#define TTR3_TS23 0x01
+
+#define TTR4_TS24 0x80
+#define TTR4_TS25 0x40
+#define TTR4_TS26 0x20
+#define TTR4_TS27 0x10
+#define TTR4_TS28 0x08
+#define TTR4_TS29 0x04
+#define TTR4_TS30 0x02
+#define TTR4_TS31 0x01
+
+
+
+/* IMR0-4 (Interrupt Mask Register 0-4)
+
+ ----------------- E1 & T1 ----------------------------- */
+
+#define IMR0_RME 0x80
+#define IMR0_RFS 0x40
+#define IMR0_T8MS 0x20
+#define IMR0_ISF 0x20
+#define IMR0_RMB 0x10
+#define IMR0_CASC 0x08
+#define IMR0_RSC 0x08
+#define IMR0_CRC6 0x04
+#define IMR0_CRC4 0x04
+#define IMR0_PDEN 0x02
+#define IMR0_RPF 0x01
+
+#define IMR1_CASE 0x80
+#define IMR1_RDO 0x40
+#define IMR1_ALLS 0x20
+#define IMR1_XDU 0x10
+#define IMR1_XMB 0x08
+#define IMR1_XLSC 0x02
+#define IMR1_XPR 0x01
+#define IMR1_LLBSC 0x80
+
+#define IMR2_FAR 0x80
+#define IMR2_LFA 0x40
+#define IMR2_MFAR 0x20
+#define IMR2_T400MS 0x10
+#define IMR2_LMFA 0x10
+#define IMR2_AIS 0x08
+#define IMR2_LOS 0x04
+#define IMR2_RAR 0x02
+#define IMR2_RA 0x01
+
+#define IMR3_ES 0x80
+#define IMR3_SEC 0x40
+#define IMR3_LMFA16 0x20
+#define IMR3_AIS16 0x10
+#define IMR3_RA16 0x08
+#define IMR3_API 0x04
+#define IMR3_XSLP 0x20
+#define IMR3_XSLN 0x10
+#define IMR3_LLBSC 0x08
+#define IMR3_XRS 0x04
+#define IMR3_SLN 0x02
+#define IMR3_SLP 0x01
+
+#define IMR4_LFA 0x80
+#define IMR4_FER 0x40
+#define IMR4_CER 0x20
+#define IMR4_AIS 0x10
+#define IMR4_LOS 0x08
+#define IMR4_CVE 0x04
+#define IMR4_SLIP 0x02
+#define IMR4_EBE 0x01
+
+/* FMR0-5 for E1 and T1 (Framer Mode Register ) */
+
+#define FMR0_XC1 0x80
+#define FMR0_XC0 0x40
+#define FMR0_RC1 0x20
+#define FMR0_RC0 0x10
+#define FMR0_EXTD 0x08
+#define FMR0_ALM 0x04
+#define E1_FMR0_FRS 0x02
+#define T1_FMR0_FRS 0x08
+#define FMR0_SRAF 0x04
+#define FMR0_EXLS 0x02
+#define FMR0_SIM 0x01
+
+#define FMR1_MFCS 0x80
+#define FMR1_AFR 0x40
+#define FMR1_ENSA 0x20
+#define FMR1_CTM 0x80
+#define FMR1_SIGM 0x40
+#define FMR1_EDL 0x20
+#define FMR1_PMOD 0x10
+#define FMR1_XFS 0x08
+#define FMR1_CRC 0x08
+#define FMR1_ECM 0x04
+#define FMR1_IMOD 0x02
+#define FMR1_XAIS 0x01
+
+#define FMR2_RFS1 0x80
+#define FMR2_RFS0 0x40
+#define FMR2_MCSP 0x40
+#define FMR2_RTM 0x20
+#define FMR2_SSP 0x20
+#define FMR2_DAIS 0x10
+#define FMR2_SAIS 0x08
+#define FMR2_PLB 0x04
+#define FMR2_AXRA 0x02
+#define FMR2_ALMF 0x01
+#define FMR2_EXZE 0x01
+
+#define LOOP_RTM 0x40
+#define LOOP_SFM 0x40
+#define LOOP_ECLB 0x20
+#define LOOP_CLA 0x1f
+
+/*--------------------- E1 ----------------------------*/
+#define FMR3_XLD 0x20
+#define FMR3_XLU 0x10
+
+/*--------------------- T1 ----------------------------*/
+#define FMR4_AIS3 0x80
+#define FMR4_TM 0x40
+#define FMR4_XRA 0x20
+#define FMR4_SSC1 0x10
+#define FMR4_SSC0 0x08
+#define FMR4_AUTO 0x04
+#define FMR4_FM1 0x02
+#define FMR4_FM0 0x01
+
+#define FMR5_SRS 0x80
+#define FMR5_EIBR 0x40
+#define FMR5_XLD 0x20
+#define FMR5_XLU 0x10
+
+
+/* LOOP (Channel Loop Back)
+
+ ------------------ E1 & T1 ---------------------------- */
+
+#define LOOP_SFM 0x40
+#define LOOP_ECLB 0x20
+#define LOOP_CLA4 0x10
+#define LOOP_CLA3 0x08
+#define LOOP_CLA2 0x04
+#define LOOP_CLA1 0x02
+#define LOOP_CLA0 0x01
+
+
+
+/* XSW (Transmit Service Word Pulseframe)
+
+ ------------------- E1 --------------------------- */
+
+#define XSW_XSIS 0x80
+#define XSW_XTM 0x40
+#define XSW_XRA 0x20
+#define XSW_XY0 0x10
+#define XSW_XY1 0x08
+#define XSW_XY2 0x04
+#define XSW_XY3 0x02
+#define XSW_XY4 0x01
+
+
+/* XSP (Transmit Spare Bits)
+
+ ------------------- E1 --------------------------- */
+
+#define XSP_XAP 0x80
+#define XSP_CASEN 0x40
+#define XSP_TT0 0x20
+#define XSP_EBP 0x10
+#define XSP_AXS 0x08
+#define XSP_XSIF 0x04
+#define XSP_XS13 0x02
+#define XSP_XS15 0x01
+
+
+/* XC0/1 (Transmit Control 0/1)
+ ------------------ E1 & T1 ---------------------------- */
+
+#define XC0_SA8E 0x80
+#define XC0_SA7E 0x40
+#define XC0_SA6E 0x20
+#define XC0_SA5E 0x10
+#define XC0_SA4E 0x08
+#define XC0_BRM 0x80
+#define XC0_MFBS 0x40
+#define XC0_SFRZ 0x10
+#define XC0_XCO2 0x04
+#define XC0_XCO1 0x02
+#define XC0_XCO0 0x01
+
+#define XC1_XTO5 0x20
+#define XC1_XTO4 0x10
+#define XC1_XTO3 0x08
+#define XC1_XTO2 0x04
+#define XC1_XTO1 0x02
+#define XC1_XTO0 0x01
+
+
+/* RC0/1 (Receive Control 0/1)
+ ------------------ E1 & T1 ---------------------------- */
+
+#define RC0_SICS 0x40
+#define RC0_CRCI 0x20
+#define RC0_XCRCI 0x10
+#define RC0_RDIS 0x08
+#define RC0_RCO2 0x04
+#define RC0_RCO1 0x02
+#define RC0_RCO0 0x01
+
+#define RC1_SWD 0x80
+#define RC1_ASY4 0x40
+#define RC1_RRAM 0x40
+#define RC1_RTO5 0x20
+#define RC1_RTO4 0x10
+#define RC1_RTO3 0x08
+#define RC1_RTO2 0x04
+#define RC1_RTO1 0x02
+#define RC1_RTO0 0x01
+
+
+
+/* XPM0-2 (Transmit Pulse Mask 0-2)
+ --------------------- E1 & T1 ------------------------- */
+
+#define XPM0_XP12 0x80
+#define XPM0_XP11 0x40
+#define XPM0_XP10 0x20
+#define XPM0_XP04 0x10
+#define XPM0_XP03 0x08
+#define XPM0_XP02 0x04
+#define XPM0_XP01 0x02
+#define XPM0_XP00 0x01
+
+#define XPM1_XP30 0x80
+#define XPM1_XP24 0x40
+#define XPM1_XP23 0x20
+#define XPM1_XP22 0x10
+#define XPM1_XP21 0x08
+#define XPM1_XP20 0x04
+#define XPM1_XP14 0x02
+#define XPM1_XP13 0x01
+
+#define XPM2_XLHP 0x80
+#define XPM2_XLT 0x40
+#define XPM2_DAXLT 0x20
+#define XPM2_XP34 0x08
+#define XPM2_XP33 0x04
+#define XPM2_XP32 0x02
+#define XPM2_XP31 0x01
+
+
+/* TSWM (Transparent Service Word Mask)
+ ------------------ E1 ---------------------------- */
+
+#define TSWM_TSIS 0x80
+#define TSWM_TSIF 0x40
+#define TSWM_TRA 0x20
+#define TSWM_TSA4 0x10
+#define TSWM_TSA5 0x08
+#define TSWM_TSA6 0x04
+#define TSWM_TSA7 0x02
+#define TSWM_TSA8 0x01
+
+/* IDLE <Idle Channel Code Register>
+
+ ------------------ E1 & T1 ----------------------- */
+
+#define IDLE_IDL7 0x80
+#define IDLE_IDL6 0x40
+#define IDLE_IDL5 0x20
+#define IDLE_IDL4 0x10
+#define IDLE_IDL3 0x08
+#define IDLE_IDL2 0x04
+#define IDLE_IDL1 0x02
+#define IDLE_IDL0 0x01
+
+
+/* XSA4-8 <Transmit SA4-8 Register(Read/Write) >
+ -------------------E1 ----------------------------- */
+
+#define XSA4_XS47 0x80
+#define XSA4_XS46 0x40
+#define XSA4_XS45 0x20
+#define XSA4_XS44 0x10
+#define XSA4_XS43 0x08
+#define XSA4_XS42 0x04
+#define XSA4_XS41 0x02
+#define XSA4_XS40 0x01
+
+#define XSA5_XS57 0x80
+#define XSA5_XS56 0x40
+#define XSA5_XS55 0x20
+#define XSA5_XS54 0x10
+#define XSA5_XS53 0x08
+#define XSA5_XS52 0x04
+#define XSA5_XS51 0x02
+#define XSA5_XS50 0x01
+
+#define XSA6_XS67 0x80
+#define XSA6_XS66 0x40
+#define XSA6_XS65 0x20
+#define XSA6_XS64 0x10
+#define XSA6_XS63 0x08
+#define XSA6_XS62 0x04
+#define XSA6_XS61 0x02
+#define XSA6_XS60 0x01
+
+#define XSA7_XS77 0x80
+#define XSA7_XS76 0x40
+#define XSA7_XS75 0x20
+#define XSA7_XS74 0x10
+#define XSA7_XS73 0x08
+#define XSA7_XS72 0x04
+#define XSA7_XS71 0x02
+#define XSA7_XS70 0x01
+
+#define XSA8_XS87 0x80
+#define XSA8_XS86 0x40
+#define XSA8_XS85 0x20
+#define XSA8_XS84 0x10
+#define XSA8_XS83 0x08
+#define XSA8_XS82 0x04
+#define XSA8_XS81 0x02
+#define XSA8_XS80 0x01
+
+
+/* XDL1-3 (Transmit DL-Bit Register1-3 (read/write))
+ ----------------------- T1 --------------------- */
+
+#define XDL1_XDL17 0x80
+#define XDL1_XDL16 0x40
+#define XDL1_XDL15 0x20
+#define XDL1_XDL14 0x10
+#define XDL1_XDL13 0x08
+#define XDL1_XDL12 0x04
+#define XDL1_XDL11 0x02
+#define XDL1_XDL10 0x01
+
+#define XDL2_XDL27 0x80
+#define XDL2_XDL26 0x40
+#define XDL2_XDL25 0x20
+#define XDL2_XDL24 0x10
+#define XDL2_XDL23 0x08
+#define XDL2_XDL22 0x04
+#define XDL2_XDL21 0x02
+#define XDL2_XDL20 0x01
+
+#define XDL3_XDL37 0x80
+#define XDL3_XDL36 0x40
+#define XDL3_XDL35 0x20
+#define XDL3_XDL34 0x10
+#define XDL3_XDL33 0x08
+#define XDL3_XDL32 0x04
+#define XDL3_XDL31 0x02
+#define XDL3_XDL30 0x01
+
+
+/* ICB1-4 (Idle Channel Register 1-4)
+ ------------------ E1 ---------------------------- */
+
+#define E1_ICB1_IC0 0x80
+#define E1_ICB1_IC1 0x40
+#define E1_ICB1_IC2 0x20
+#define E1_ICB1_IC3 0x10
+#define E1_ICB1_IC4 0x08
+#define E1_ICB1_IC5 0x04
+#define E1_ICB1_IC6 0x02
+#define E1_ICB1_IC7 0x01
+
+#define E1_ICB2_IC8 0x80
+#define E1_ICB2_IC9 0x40
+#define E1_ICB2_IC10 0x20
+#define E1_ICB2_IC11 0x10
+#define E1_ICB2_IC12 0x08
+#define E1_ICB2_IC13 0x04
+#define E1_ICB2_IC14 0x02
+#define E1_ICB2_IC15 0x01
+
+#define E1_ICB3_IC16 0x80
+#define E1_ICB3_IC17 0x40
+#define E1_ICB3_IC18 0x20
+#define E1_ICB3_IC19 0x10
+#define E1_ICB3_IC20 0x08
+#define E1_ICB3_IC21 0x04
+#define E1_ICB3_IC22 0x02
+#define E1_ICB3_IC23 0x01
+
+#define E1_ICB4_IC24 0x80
+#define E1_ICB4_IC25 0x40
+#define E1_ICB4_IC26 0x20
+#define E1_ICB4_IC27 0x10
+#define E1_ICB4_IC28 0x08
+#define E1_ICB4_IC29 0x04
+#define E1_ICB4_IC30 0x02
+#define E1_ICB4_IC31 0x01
+
+/* ICB1-4 (Idle Channel Register 1-4)
+ ------------------ T1 ---------------------------- */
+
+#define T1_ICB1_IC1 0x80
+#define T1_ICB1_IC2 0x40
+#define T1_ICB1_IC3 0x20
+#define T1_ICB1_IC4 0x10
+#define T1_ICB1_IC5 0x08
+#define T1_ICB1_IC6 0x04
+#define T1_ICB1_IC7 0x02
+#define T1_ICB1_IC8 0x01
+
+#define T1_ICB2_IC9 0x80
+#define T1_ICB2_IC10 0x40
+#define T1_ICB2_IC11 0x20
+#define T1_ICB2_IC12 0x10
+#define T1_ICB2_IC13 0x08
+#define T1_ICB2_IC14 0x04
+#define T1_ICB2_IC15 0x02
+#define T1_ICB2_IC16 0x01
+
+#define T1_ICB3_IC17 0x80
+#define T1_ICB3_IC18 0x40
+#define T1_ICB3_IC19 0x20
+#define T1_ICB3_IC20 0x10
+#define T1_ICB3_IC21 0x08
+#define T1_ICB3_IC22 0x04
+#define T1_ICB3_IC23 0x02
+#define T1_ICB3_IC24 0x01
+
+/* FMR3 (Framer Mode Register 3)
+ --------------------E1------------------------ */
+
+#define FMR3_CMI 0x08
+#define FMR3_SYNSA 0x04
+#define FMR3_CFRZ 0x02
+#define FMR3_EXTIW 0x01
+
+
+
+/* CCB1-3 (Clear Channel Register)
+ ------------------- T1 ----------------------- */
+
+#define CCB1_CH1 0x80
+#define CCB1_CH2 0x40
+#define CCB1_CH3 0x20
+#define CCB1_CH4 0x10
+#define CCB1_CH5 0x08
+#define CCB1_CH6 0x04
+#define CCB1_CH7 0x02
+#define CCB1_CH8 0x01
+
+#define CCB2_CH9 0x80
+#define CCB2_CH10 0x40
+#define CCB2_CH11 0x20
+#define CCB2_CH12 0x10
+#define CCB2_CH13 0x08
+#define CCB2_CH14 0x04
+#define CCB2_CH15 0x02
+#define CCB2_CH16 0x01
+
+#define CCB3_CH17 0x80
+#define CCB3_CH18 0x40
+#define CCB3_CH19 0x20
+#define CCB3_CH20 0x10
+#define CCB3_CH21 0x08
+#define CCB3_CH22 0x04
+#define CCB3_CH23 0x02
+#define CCB3_CH24 0x01
+
+
+/* LIM0/1 (Line Interface Mode 0/1)
+ ------------------- E1 & T1 --------------------------- */
+
+#define LIM0_XFB 0x80
+#define LIM0_XDOS 0x40
+#define LIM0_SCL1 0x20
+#define LIM0_SCL0 0x10
+#define LIM0_EQON 0x08
+#define LIM0_ELOS 0x04
+#define LIM0_LL 0x02
+#define LIM0_MAS 0x01
+
+#define LIM1_EFSC 0x80
+#define LIM1_RIL2 0x40
+#define LIM1_RIL1 0x20
+#define LIM1_RIL0 0x10
+#define LIM1_DCOC 0x08
+#define LIM1_JATT 0x04
+#define LIM1_RL 0x02
+#define LIM1_DRS 0x01
+
+
+/* PCDR (Pulse Count Detection Register(Read/Write))
+ ------------------ E1 & T1 ------------------------- */
+
+#define PCDR_PCD7 0x80
+#define PCDR_PCD6 0x40
+#define PCDR_PCD5 0x20
+#define PCDR_PCD4 0x10
+#define PCDR_PCD3 0x08
+#define PCDR_PCD2 0x04
+#define PCDR_PCD1 0x02
+#define PCDR_PCD0 0x01
+
+#define PCRR_PCR7 0x80
+#define PCRR_PCR6 0x40
+#define PCRR_PCR5 0x20
+#define PCRR_PCR4 0x10
+#define PCRR_PCR3 0x08
+#define PCRR_PCR2 0x04
+#define PCRR_PCR1 0x02
+#define PCRR_PCR0 0x01
+
+
+/* LIM2 (Line Interface Mode 2)
+
+ ------------------ E1 & T1 ---------------------------- */
+
+#define LIM2_DJA2 0x20
+#define LIM2_DJA1 0x10
+#define LIM2_LOS2 0x02
+#define LIM2_LOS1 0x01
+
+/* LCR1 (Loop Code Register 1) */
+
+#define LCR1_EPRM 0x80
+#define LCR1_XPRBS 0x40
+
+/* SIC1 (System Interface Control 1) */
+#define SIC1_SRSC 0x80
+#define SIC1_RBS1 0x20
+#define SIC1_RBS0 0x10
+#define SIC1_SXSC 0x08
+#define SIC1_XBS1 0x02
+#define SIC1_XBS0 0x01
+
+/* DEC (Disable Error Counter)
+ ------------------ E1 & T1 ---------------------------- */
+
+#define DEC_DCEC3 0x20
+#define DEC_DBEC 0x10
+#define DEC_DCEC1 0x08
+#define DEC_DCEC 0x08
+#define DEC_DEBC 0x04
+#define DEC_DCVC 0x02
+#define DEC_DFEC 0x01
+
+
+/* FALC Register Bits (Receive Mode)
+ ---------------------------------------------------------------------------- */
+
+
+/* FRS0/1 (Framer Receive Status Register 0/1)
+ ----------------- E1 & T1 ---------------------------------- */
+
+#define FRS0_LOS 0x80
+#define FRS0_AIS 0x40
+#define FRS0_LFA 0x20
+#define FRS0_RRA 0x10
+#define FRS0_API 0x08
+#define FRS0_NMF 0x04
+#define FRS0_LMFA 0x02
+#define FRS0_FSRF 0x01
+
+#define FRS1_TS16RA 0x40
+#define FRS1_TS16LOS 0x20
+#define FRS1_TS16AIS 0x10
+#define FRS1_TS16LFA 0x08
+#define FRS1_EXZD 0x80
+#define FRS1_LLBDD 0x10
+#define FRS1_LLBAD 0x08
+#define FRS1_XLS 0x02
+#define FRS1_XLO 0x01
+#define FRS1_PDEN 0x40
+
+/* FRS2/3 (Framer Receive Status Register 2/3)
+ ----------------- T1 ---------------------------------- */
+
+#define FRS2_ESC2 0x80
+#define FRS2_ESC1 0x40
+#define FRS2_ESC0 0x20
+
+#define FRS3_FEH5 0x20
+#define FRS3_FEH4 0x10
+#define FRS3_FEH3 0x08
+#define FRS3_FEH2 0x04
+#define FRS3_FEH1 0x02
+#define FRS3_FEH0 0x01
+
+
+/* RSW (Receive Service Word Pulseframe)
+ ----------------- E1 ------------------------------ */
+
+#define RSW_RSI 0x80
+#define RSW_RRA 0x20
+#define RSW_RYO 0x10
+#define RSW_RY1 0x08
+#define RSW_RY2 0x04
+#define RSW_RY3 0x02
+#define RSW_RY4 0x01
+
+
+/* RSP (Receive Spare Bits / Additional Status)
+ ---------------- E1 ------------------------------- */
+
+#define RSP_SI1 0x80
+#define RSP_SI2 0x40
+#define RSP_LLBDD 0x10
+#define RSP_LLBAD 0x08
+#define RSP_RSIF 0x04
+#define RSP_RS13 0x02
+#define RSP_RS15 0x01
+
+
+/* FECL (Framing Error Counter)
+ ---------------- E1 & T1 -------------------------- */
+
+#define FECL_FE7 0x80
+#define FECL_FE6 0x40
+#define FECL_FE5 0x20
+#define FECL_FE4 0x10
+#define FECL_FE3 0x08
+#define FECL_FE2 0x04
+#define FECL_FE1 0x02
+#define FECL_FE0 0x01
+
+#define FECH_FE15 0x80
+#define FECH_FE14 0x40
+#define FECH_FE13 0x20
+#define FECH_FE12 0x10
+#define FECH_FE11 0x08
+#define FECH_FE10 0x04
+#define FECH_FE9 0x02
+#define FECH_FE8 0x01
+
+
+/* CVCl (Code Violation Counter)
+ ----------------- E1 ------------------------- */
+
+#define CVCL_CV7 0x80
+#define CVCL_CV6 0x40
+#define CVCL_CV5 0x20
+#define CVCL_CV4 0x10
+#define CVCL_CV3 0x08
+#define CVCL_CV2 0x04
+#define CVCL_CV1 0x02
+#define CVCL_CV0 0x01
+
+#define CVCH_CV15 0x80
+#define CVCH_CV14 0x40
+#define CVCH_CV13 0x20
+#define CVCH_CV12 0x10
+#define CVCH_CV11 0x08
+#define CVCH_CV10 0x04
+#define CVCH_CV9 0x02
+#define CVCH_CV8 0x01
+
+
+/* CEC1-3L (CRC Error Counter)
+ ------------------ E1 ----------------------------- */
+
+#define CEC1L_CR7 0x80
+#define CEC1L_CR6 0x40
+#define CEC1L_CR5 0x20
+#define CEC1L_CR4 0x10
+#define CEC1L_CR3 0x08
+#define CEC1L_CR2 0x04
+#define CEC1L_CR1 0x02
+#define CEC1L_CR0 0x01
+
+#define CEC1H_CR15 0x80
+#define CEC1H_CR14 0x40
+#define CEC1H_CR13 0x20
+#define CEC1H_CR12 0x10
+#define CEC1H_CR11 0x08
+#define CEC1H_CR10 0x04
+#define CEC1H_CR9 0x02
+#define CEC1H_CR8 0x01
+
+#define CEC2L_CR7 0x80
+#define CEC2L_CR6 0x40
+#define CEC2L_CR5 0x20
+#define CEC2L_CR4 0x10
+#define CEC2L_CR3 0x08
+#define CEC2L_CR2 0x04
+#define CEC2L_CR1 0x02
+#define CEC2L_CR0 0x01
+
+#define CEC2H_CR15 0x80
+#define CEC2H_CR14 0x40
+#define CEC2H_CR13 0x20
+#define CEC2H_CR12 0x10
+#define CEC2H_CR11 0x08
+#define CEC2H_CR10 0x04
+#define CEC2H_CR9 0x02
+#define CEC2H_CR8 0x01
+
+#define CEC3L_CR7 0x80
+#define CEC3L_CR6 0x40
+#define CEC3L_CR5 0x20
+#define CEC3L_CR4 0x10
+#define CEC3L_CR3 0x08
+#define CEC3L_CR2 0x04
+#define CEC3L_CR1 0x02
+#define CEC3L_CR0 0x01
+
+#define CEC3H_CR15 0x80
+#define CEC3H_CR14 0x40
+#define CEC3H_CR13 0x20
+#define CEC3H_CR12 0x10
+#define CEC3H_CR11 0x08
+#define CEC3H_CR10 0x04
+#define CEC3H_CR9 0x02
+#define CEC3H_CR8 0x01
+
+
+/* CECL (CRC Error Counter)
+
+ ------------------ T1 ----------------------------- */
+
+#define CECL_CR7 0x80
+#define CECL_CR6 0x40
+#define CECL_CR5 0x20
+#define CECL_CR4 0x10
+#define CECL_CR3 0x08
+#define CECL_CR2 0x04
+#define CECL_CR1 0x02
+#define CECL_CR0 0x01
+
+#define CECH_CR15 0x80
+#define CECH_CR14 0x40
+#define CECH_CR13 0x20
+#define CECH_CR12 0x10
+#define CECH_CR11 0x08
+#define CECH_CR10 0x04
+#define CECH_CR9 0x02
+#define CECH_CR8 0x01
+
+/* EBCL (E Bit Error Counter)
+ ------------------- E1 & T1 ------------------------- */
+
+#define EBCL_EB7 0x80
+#define EBCL_EB6 0x40
+#define EBCL_EB5 0x20
+#define EBCL_EB4 0x10
+#define EBCL_EB3 0x08
+#define EBCL_EB2 0x04
+#define EBCL_EB1 0x02
+#define EBCL_EB0 0x01
+
+#define EBCH_EB15 0x80
+#define EBCH_EB14 0x40
+#define EBCH_EB13 0x20
+#define EBCH_EB12 0x10
+#define EBCH_EB11 0x08
+#define EBCH_EB10 0x04
+#define EBCH_EB9 0x02
+#define EBCH_EB8 0x01
+
+
+/* RSA4-8 (Receive Sa4-8-Bit Register)
+ -------------------- E1 --------------------------- */
+
+#define RSA4_RS47 0x80
+#define RSA4_RS46 0x40
+#define RSA4_RS45 0x20
+#define RSA4_RS44 0x10
+#define RSA4_RS43 0x08
+#define RSA4_RS42 0x04
+#define RSA4_RS41 0x02
+#define RSA4_RS40 0x01
+
+#define RSA5_RS57 0x80
+#define RSA5_RS56 0x40
+#define RSA5_RS55 0x20
+#define RSA5_RS54 0x10
+#define RSA5_RS53 0x08
+#define RSA5_RS52 0x04
+#define RSA5_RS51 0x02
+#define RSA5_RS50 0x01
+
+#define RSA6_RS67 0x80
+#define RSA6_RS66 0x40
+#define RSA6_RS65 0x20
+#define RSA6_RS64 0x10
+#define RSA6_RS63 0x08
+#define RSA6_RS62 0x04
+#define RSA6_RS61 0x02
+#define RSA6_RS60 0x01
+
+#define RSA7_RS77 0x80
+#define RSA7_RS76 0x40
+#define RSA7_RS75 0x20
+#define RSA7_RS74 0x10
+#define RSA7_RS73 0x08
+#define RSA7_RS72 0x04
+#define RSA7_RS71 0x02
+#define RSA7_RS70 0x01
+
+#define RSA8_RS87 0x80
+#define RSA8_RS86 0x40
+#define RSA8_RS85 0x20
+#define RSA8_RS84 0x10
+#define RSA8_RS83 0x08
+#define RSA8_RS82 0x04
+#define RSA8_RS81 0x02
+#define RSA8_RS80 0x01
+
+/* RSA6S (Receive Sa6 Bit Status Register)
+ ------------------------ T1 ------------------------- */
+
+#define RSA6S_SX 0x20
+#define RSA6S_SF 0x10
+#define RSA6S_SE 0x08
+#define RSA6S_SC 0x04
+#define RSA6S_SA 0x02
+#define RSA6S_S8 0x01
+
+
+/* RDL1-3 Receive DL-Bit Register1-3)
+ ------------------------ T1 ------------------------- */
+
+#define RDL1_RDL17 0x80
+#define RDL1_RDL16 0x40
+#define RDL1_RDL15 0x20
+#define RDL1_RDL14 0x10
+#define RDL1_RDL13 0x08
+#define RDL1_RDL12 0x04
+#define RDL1_RDL11 0x02
+#define RDL1_RDL10 0x01
+
+#define RDL2_RDL27 0x80
+#define RDL2_RDL26 0x40
+#define RDL2_RDL25 0x20
+#define RDL2_RDL24 0x10
+#define RDL2_RDL23 0x08
+#define RDL2_RDL22 0x04
+#define RDL2_RDL21 0x02
+#define RDL2_RDL20 0x01
+
+#define RDL3_RDL37 0x80
+#define RDL3_RDL36 0x40
+#define RDL3_RDL35 0x20
+#define RDL3_RDL34 0x10
+#define RDL3_RDL33 0x08
+#define RDL3_RDL32 0x04
+#define RDL3_RDL31 0x02
+#define RDL3_RDL30 0x01
+
+
+/* SIS (Signaling Status Register)
+
+ -------------------- E1 & T1 -------------------------- */
+
+#define SIS_XDOV 0x80
+#define SIS_XFW 0x40
+#define SIS_XREP 0x20
+#define SIS_RLI 0x08
+#define SIS_CEC 0x04
+#define SIS_BOM 0x01
+
+
+/* RSIS (Receive Signaling Status Register)
+
+ -------------------- E1 & T1 --------------------------- */
+
+#define RSIS_VFR 0x80
+#define RSIS_RDO 0x40
+#define RSIS_CRC16 0x20
+#define RSIS_RAB 0x10
+#define RSIS_HA1 0x08
+#define RSIS_HA0 0x04
+#define RSIS_HFR 0x02
+#define RSIS_LA 0x01
+
+
+/* RBCL/H (Receive Byte Count Low/High)
+
+ ------------------- E1 & T1 ----------------------- */
+
+#define RBCL_RBC7 0x80
+#define RBCL_RBC6 0x40
+#define RBCL_RBC5 0x20
+#define RBCL_RBC4 0x10
+#define RBCL_RBC3 0x08
+#define RBCL_RBC2 0x04
+#define RBCL_RBC1 0x02
+#define RBCL_RBC0 0x01
+
+#define RBCH_OV 0x10
+#define RBCH_RBC11 0x08
+#define RBCH_RBC10 0x04
+#define RBCH_RBC9 0x02
+#define RBCH_RBC8 0x01
+
+
+/* ISR1-3 (Interrupt Status Register 1-3)
+
+ ------------------ E1 & T1 ------------------------------ */
+
+#define FISR0_RME 0x80
+#define FISR0_RFS 0x40
+#define FISR0_T8MS 0x20
+#define FISR0_ISF 0x20
+#define FISR0_RMB 0x10
+#define FISR0_CASC 0x08
+#define FISR0_RSC 0x08
+#define FISR0_CRC6 0x04
+#define FISR0_CRC4 0x04
+#define FISR0_PDEN 0x02
+#define FISR0_RPF 0x01
+
+#define FISR1_CASE 0x80
+#define FISR1_LLBSC 0x80
+#define FISR1_RDO 0x40
+#define FISR1_ALLS 0x20
+#define FISR1_XDU 0x10
+#define FISR1_XMB 0x08
+#define FISR1_XLSC 0x02
+#define FISR1_XPR 0x01
+
+#define FISR2_FAR 0x80
+#define FISR2_LFA 0x40
+#define FISR2_MFAR 0x20
+#define FISR2_T400MS 0x10
+#define FISR2_LMFA 0x10
+#define FISR2_AIS 0x08
+#define FISR2_LOS 0x04
+#define FISR2_RAR 0x02
+#define FISR2_RA 0x01
+
+#define FISR3_ES 0x80
+#define FISR3_SEC 0x40
+#define FISR3_LMFA16 0x20
+#define FISR3_AIS16 0x10
+#define FISR3_RA16 0x08
+#define FISR3_API 0x04
+#define FISR3_XSLP 0x20
+#define FISR3_XSLN 0x10
+#define FISR3_LLBSC 0x08
+#define FISR3_XRS 0x04
+#define FISR3_SLN 0x02
+#define FISR3_SLP 0x01
+
+
+/* GIS (Global Interrupt Status Register)
+
+ --------------------- E1 & T1 --------------------- */
+
+#define GIS_ISR3 0x08
+#define GIS_ISR2 0x04
+#define GIS_ISR1 0x02
+#define GIS_ISR0 0x01
+
+
+/* VSTR (Version Status Register)
+
+ --------------------- E1 & T1 --------------------- */
+
+#define VSTR_VN3 0x08
+#define VSTR_VN2 0x04
+#define VSTR_VN1 0x02
+#define VSTR_VN0 0x01
+
+
+/*>>>>>>>>>>>>>>>>>>>>> Local Control Structures <<<<<<<<<<<<<<<<<<<<<<<<< */
+
+/* Write-only Registers (E1/T1 control mode write registers) */
+#define XFIFOH 0x00 /* Tx FIFO High Byte */
+#define XFIFOL 0x01 /* Tx FIFO Low Byte */
+#define CMDR 0x02 /* Command Reg */
+#define DEC 0x60 /* Disable Error Counter */
+#define TEST2 0x62 /* Manuf. Test Reg 2 */
+#define XS(nbr) (0x70 + (nbr)) /* Tx CAS Reg (0 to 15) */
+
+/* Read-write Registers (E1/T1 status mode read registers) */
+#define MODE 0x03 /* Mode Reg */
+#define RAH1 0x04 /* Receive Address High 1 */
+#define RAH2 0x05 /* Receive Address High 2 */
+#define RAL1 0x06 /* Receive Address Low 1 */
+#define RAL2 0x07 /* Receive Address Low 2 */
+#define IPC 0x08 /* Interrupt Port Configuration */
+#define CCR1 0x09 /* Common Configuration Reg 1 */
+#define CCR3 0x0A /* Common Configuration Reg 3 */
+#define PRE 0x0B /* Preamble Reg */
+#define RTR1 0x0C /* Receive Timeslot Reg 1 */
+#define RTR2 0x0D /* Receive Timeslot Reg 2 */
+#define RTR3 0x0E /* Receive Timeslot Reg 3 */
+#define RTR4 0x0F /* Receive Timeslot Reg 4 */
+#define TTR1 0x10 /* Transmit Timeslot Reg 1 */
+#define TTR2 0x11 /* Transmit Timeslot Reg 2 */
+#define TTR3 0x12 /* Transmit Timeslot Reg 3 */
+#define TTR4 0x13 /* Transmit Timeslot Reg 4 */
+#define IMR0 0x14 /* Interrupt Mask Reg 0 */
+#define IMR1 0x15 /* Interrupt Mask Reg 1 */
+#define IMR2 0x16 /* Interrupt Mask Reg 2 */
+#define IMR3 0x17 /* Interrupt Mask Reg 3 */
+#define IMR4 0x18 /* Interrupt Mask Reg 4 */
+#define IMR5 0x19 /* Interrupt Mask Reg 5 */
+#define FMR0 0x1A /* Framer Mode Reigster 0 */
+#define FMR1 0x1B /* Framer Mode Reigster 1 */
+#define FMR2 0x1C /* Framer Mode Reigster 2 */
+#define LOOP 0x1D /* Channel Loop Back */
+#define XSW 0x1E /* Transmit Service Word */
+#define FMR4 0x1E /* Framer Mode Reg 4 */
+#define XSP 0x1F /* Transmit Spare Bits */
+#define FMR5 0x1F /* Framer Mode Reg 5 */
+#define XC0 0x20 /* Transmit Control 0 */
+#define XC1 0x21 /* Transmit Control 1 */
+#define RC0 0x22 /* Receive Control 0 */
+#define RC1 0x23 /* Receive Control 1 */
+#define XPM0 0x24 /* Transmit Pulse Mask 0 */
+#define XPM1 0x25 /* Transmit Pulse Mask 1 */
+#define XPM2 0x26 /* Transmit Pulse Mask 2 */
+#define TSWM 0x27 /* Transparent Service Word Mask */
+#define TEST1 0x28 /* Manuf. Test Reg 1 */
+#define IDLE 0x29 /* Idle Channel Code */
+#define XSA4 0x2A /* Transmit SA4 Bit Reg */
+#define XDL1 0x2A /* Transmit DL-Bit Reg 2 */
+#define XSA5 0x2B /* Transmit SA4 Bit Reg */
+#define XDL2 0x2B /* Transmit DL-Bit Reg 2 */
+#define XSA6 0x2C /* Transmit SA4 Bit Reg */
+#define XDL3 0x2C /* Transmit DL-Bit Reg 2 */
+#define XSA7 0x2D /* Transmit SA4 Bit Reg */
+#define CCB1 0x2D /* Clear Channel Reg 1 */
+#define XSA8 0x2E /* Transmit SA4 Bit Reg */
+#define CCB2 0x2E /* Clear Channel Reg 2 */
+#define FMR3 0x2F /* Framer Mode Reg. 3 */
+#define CCB3 0x2F /* Clear Channel Reg 3 */
+#define ICB1 0x30 /* Idle Channel Reg 1 */
+#define ICB2 0x31 /* Idle Channel Reg 2 */
+#define ICB3 0x32 /* Idle Channel Reg 3 */
+#define ICB4 0x33 /* Idle Channel Reg 4 */
+#define LIM0 0x34 /* Line Interface Mode 0 */
+#define LIM1 0x35 /* Line Interface Mode 1 */
+#define PCDR 0x36 /* Pulse Count Detection */
+#define PCRR 0x37 /* Pulse Count Recovery */
+#define LIM2 0x38 /* Line Interface Mode Reg 2 */
+#define LCR1 0x39 /* Loop Code Reg 1 */
+#define LCR2 0x3A /* Loop Code Reg 2 */
+#define LCR3 0x3B /* Loop Code Reg 3 */
+#define SIC1 0x3C /* System Interface Control 1 */
+
+/* Read-only Registers (E1/T1 control mode read registers) */
+#define RFIFOH 0x00 /* Receive FIFO */
+#define RFIFOL 0x01 /* Receive FIFO */
+#define FRS0 0x4C /* Framer Receive Status 0 */
+#define FRS1 0x4D /* Framer Receive Status 1 */
+#define RSW 0x4E /* Receive Service Word */
+#define FRS2 0x4E /* Framer Receive Status 2 */
+#define RSP 0x4F /* Receive Spare Bits */
+#define FRS3 0x4F /* Framer Receive Status 3 */
+#define FECL 0x50 /* Framing Error Counter */
+#define FECH 0x51 /* Framing Error Counter */
+#define CVCL 0x52 /* Code Violation Counter */
+#define CVCH 0x53 /* Code Violation Counter */
+#define CECL 0x54 /* CRC Error Counter 1 */
+#define CECH 0x55 /* CRC Error Counter 1 */
+#define EBCL 0x56 /* E-Bit Error Counter */
+#define EBCH 0x57 /* E-Bit Error Counter */
+#define BECL 0x58 /* Bit Error Counter Low */
+#define BECH 0x59 /* Bit Error Counter Low */
+#define CEC3 0x5A /* CRC Error Counter 3 (16-bit) */
+#define RSA4 0x5C /* Receive SA4 Bit Reg */
+#define RDL1 0x5C /* Receive DL-Bit Reg 1 */
+#define RSA5 0x5D /* Receive SA5 Bit Reg */
+#define RDL2 0x5D /* Receive DL-Bit Reg 2 */
+#define RSA6 0x5E /* Receive SA6 Bit Reg */
+#define RDL3 0x5E /* Receive DL-Bit Reg 3 */
+#define RSA7 0x5F /* Receive SA7 Bit Reg */
+#define RSA8 0x60 /* Receive SA8 Bit Reg */
+#define RSA6S 0x61 /* Receive SA6 Bit Status Reg */
+#define TSR0 0x62 /* Manuf. Test Reg 0 */
+#define TSR1 0x63 /* Manuf. Test Reg 1 */
+#define SIS 0x64 /* Signaling Status Reg */
+#define RSIS 0x65 /* Receive Signaling Status Reg */
+#define RBCL 0x66 /* Receive Byte Control */
+#define RBCH 0x67 /* Receive Byte Control */
+#define FISR0 0x68 /* Interrupt Status Reg 0 */
+#define FISR1 0x69 /* Interrupt Status Reg 1 */
+#define FISR2 0x6A /* Interrupt Status Reg 2 */
+#define FISR3 0x6B /* Interrupt Status Reg 3 */
+#define GIS 0x6E /* Global Interrupt Status */
+#define VSTR 0x6F /* Version Status */
+#define RS(nbr) (0x70 + (nbr)) /* Rx CAS Reg (0 to 15) */
+
+#endif /* _FALC_LH_H */
+
diff --git a/drivers/net/wan/pc300.h b/drivers/net/wan/pc300.h
new file mode 100644
index 00000000000..73401b0f015
--- /dev/null
+++ b/drivers/net/wan/pc300.h
@@ -0,0 +1,497 @@
+/*
+ * pc300.h Cyclades-PC300(tm) Kernel API Definitions.
+ *
+ * Author: Ivan Passos <ivan@cyclades.com>
+ *
+ * Copyright: (c) 1999-2002 Cyclades Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Log: pc300.h,v $
+ * Revision 3.12 2002/03/07 14:17:09 henrique
+ * License data fixed
+ *
+ * Revision 3.11 2002/01/28 21:09:39 daniela
+ * Included ';' after pc300hw.bus.
+ *
+ * Revision 3.10 2002/01/17 17:58:52 ivan
+ * Support for PC300-TE/M (PMC).
+ *
+ * Revision 3.9 2001/09/28 13:30:53 daniela
+ * Renamed dma_start routine to rx_dma_start.
+ *
+ * Revision 3.8 2001/09/24 13:03:45 daniela
+ * Fixed BOF interrupt treatment. Created dma_start routine.
+ *
+ * Revision 3.7 2001/08/10 17:19:58 daniela
+ * Fixed IOCTLs defines.
+ *
+ * Revision 3.6 2001/07/18 19:24:42 daniela
+ * Included kernel version.
+ *
+ * Revision 3.5 2001/07/05 18:38:08 daniela
+ * DMA transmission bug fix.
+ *
+ * Revision 3.4 2001/06/26 17:10:40 daniela
+ * New configuration parameters (line code, CRC calculation and clock).
+ *
+ * Revision 3.3 2001/06/22 13:13:02 regina
+ * MLPPP implementation
+ *
+ * Revision 3.2 2001/06/18 17:56:09 daniela
+ * Increased DEF_MTU and TX_QUEUE_LEN.
+ *
+ * Revision 3.1 2001/06/15 12:41:10 regina
+ * upping major version number
+ *
+ * Revision 1.1.1.1 2001/06/13 20:25:06 daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 2.3 2001/03/05 daniela
+ * Created struct pc300conf, to provide the hardware information to pc300util.
+ * Inclusion of 'alloc_ramsize' field on structure 'pc300hw'.
+ *
+ * Revision 2.2 2000/12/22 daniela
+ * Structures and defines to support pc300util: statistics, status,
+ * loopback tests, trace.
+ *
+ * Revision 2.1 2000/09/28 ivan
+ * Inclusion of 'iophys' and 'iosize' fields on structure 'pc300hw', to
+ * allow release of I/O region at module unload.
+ * Changed location of include files.
+ *
+ * Revision 2.0 2000/03/27 ivan
+ * Added support for the PC300/TE cards.
+ *
+ * Revision 1.1 2000/01/31 ivan
+ * Replaced 'pc300[drv|sca].h' former PC300 driver include files.
+ *
+ * Revision 1.0 1999/12/16 ivan
+ * First official release.
+ * Inclusion of 'nchan' field on structure 'pc300hw', to allow variable
+ * number of ports per card.
+ * Inclusion of 'if_ptr' field on structure 'pc300dev'.
+ *
+ * Revision 0.6 1999/11/17 ivan
+ * Changed X.25-specific function names to comply with adopted convention.
+ *
+ * Revision 0.5 1999/11/16 Daniela Squassoni
+ * X.25 support.
+ *
+ * Revision 0.4 1999/11/15 ivan
+ * Inclusion of 'clock' field on structure 'pc300hw'.
+ *
+ * Revision 0.3 1999/11/10 ivan
+ * IOCTL name changing.
+ * Inclusion of driver function prototypes.
+ *
+ * Revision 0.2 1999/11/03 ivan
+ * Inclusion of 'tx_skb' and union 'ifu' on structure 'pc300dev'.
+ *
+ * Revision 0.1 1999/01/15 ivan
+ * Initial version.
+ *
+ */
+
+#ifndef _PC300_H
+#define _PC300_H
+
+#include <linux/hdlc.h>
+#include "hd64572.h"
+#include "pc300-falc-lh.h"
+
+#ifndef CY_TYPES
+#define CY_TYPES
+typedef __u64 ucdouble; /* 64 bits, unsigned */
+typedef __u32 uclong; /* 32 bits, unsigned */
+typedef __u16 ucshort; /* 16 bits, unsigned */
+typedef __u8 ucchar; /* 8 bits, unsigned */
+#endif /* CY_TYPES */
+
+#define PC300_PROTO_MLPPP 1
+
+#define PC300_KERNEL "2.4.x" /* Kernel supported by this driver */
+
+#define PC300_DEVNAME "hdlc" /* Dev. name base (for hdlc0, hdlc1, etc.) */
+#define PC300_MAXINDEX 100 /* Max dev. name index (the '0' in hdlc0) */
+
+#define PC300_MAXCARDS 4 /* Max number of cards per system */
+#define PC300_MAXCHAN 2 /* Number of channels per card */
+
+#define PC300_PLX_WIN 0x80 /* PLX control window size (128b) */
+#define PC300_RAMSIZE 0x40000 /* RAM window size (256Kb) */
+#define PC300_SCASIZE 0x400 /* SCA window size (1Kb) */
+#define PC300_FALCSIZE 0x400 /* FALC window size (1Kb) */
+
+#define PC300_OSC_CLOCK 24576000
+#define PC300_PCI_CLOCK 33000000
+
+#define BD_DEF_LEN 0x0800 /* DMA buffer length (2KB) */
+#define DMA_TX_MEMSZ 0x8000 /* Total DMA Tx memory size (32KB/ch) */
+#define DMA_RX_MEMSZ 0x10000 /* Total DMA Rx memory size (64KB/ch) */
+
+#define N_DMA_TX_BUF (DMA_TX_MEMSZ / BD_DEF_LEN) /* DMA Tx buffers */
+#define N_DMA_RX_BUF (DMA_RX_MEMSZ / BD_DEF_LEN) /* DMA Rx buffers */
+
+/* DMA Buffer Offsets */
+#define DMA_TX_BASE ((N_DMA_TX_BUF + N_DMA_RX_BUF) * \
+ PC300_MAXCHAN * sizeof(pcsca_bd_t))
+#define DMA_RX_BASE (DMA_TX_BASE + PC300_MAXCHAN*DMA_TX_MEMSZ)
+
+/* DMA Descriptor Offsets */
+#define DMA_TX_BD_BASE 0x0000
+#define DMA_RX_BD_BASE (DMA_TX_BD_BASE + ((PC300_MAXCHAN*DMA_TX_MEMSZ / \
+ BD_DEF_LEN) * sizeof(pcsca_bd_t)))
+
+/* DMA Descriptor Macros */
+#define TX_BD_ADDR(chan, n) (DMA_TX_BD_BASE + \
+ ((N_DMA_TX_BUF*chan) + n) * sizeof(pcsca_bd_t))
+#define RX_BD_ADDR(chan, n) (DMA_RX_BD_BASE + \
+ ((N_DMA_RX_BUF*chan) + n) * sizeof(pcsca_bd_t))
+
+/* Macro to access the FALC registers (TE only) */
+#define F_REG(reg, chan) (0x200*(chan) + ((reg)<<2))
+
+/***************************************
+ * Memory access functions/macros *
+ * (required to support Alpha systems) *
+ ***************************************/
+#ifdef __KERNEL__
+#define cpc_writeb(port,val) {writeb((ucchar)(val),(port)); mb();}
+#define cpc_writew(port,val) {writew((ushort)(val),(port)); mb();}
+#define cpc_writel(port,val) {writel((uclong)(val),(port)); mb();}
+
+#define cpc_readb(port) readb(port)
+#define cpc_readw(port) readw(port)
+#define cpc_readl(port) readl(port)
+
+#else /* __KERNEL__ */
+#define cpc_writeb(port,val) (*(volatile ucchar *)(port) = (ucchar)(val))
+#define cpc_writew(port,val) (*(volatile ucshort *)(port) = (ucshort)(val))
+#define cpc_writel(port,val) (*(volatile uclong *)(port) = (uclong)(val))
+
+#define cpc_readb(port) (*(volatile ucchar *)(port))
+#define cpc_readw(port) (*(volatile ucshort *)(port))
+#define cpc_readl(port) (*(volatile uclong *)(port))
+
+#endif /* __KERNEL__ */
+
+/****** Data Structures *****************************************************/
+
+/*
+ * RUNTIME_9050 - PLX PCI9050-1 local configuration and shared runtime
+ * registers. This structure can be used to access the 9050 registers
+ * (memory mapped).
+ */
+struct RUNTIME_9050 {
+ uclong loc_addr_range[4]; /* 00-0Ch : Local Address Ranges */
+ uclong loc_rom_range; /* 10h : Local ROM Range */
+ uclong loc_addr_base[4]; /* 14-20h : Local Address Base Addrs */
+ uclong loc_rom_base; /* 24h : Local ROM Base */
+ uclong loc_bus_descr[4]; /* 28-34h : Local Bus Descriptors */
+ uclong rom_bus_descr; /* 38h : ROM Bus Descriptor */
+ uclong cs_base[4]; /* 3C-48h : Chip Select Base Addrs */
+ uclong intr_ctrl_stat; /* 4Ch : Interrupt Control/Status */
+ uclong init_ctrl; /* 50h : EEPROM ctrl, Init Ctrl, etc */
+};
+
+#define PLX_9050_LINT1_ENABLE 0x01
+#define PLX_9050_LINT1_POL 0x02
+#define PLX_9050_LINT1_STATUS 0x04
+#define PLX_9050_LINT2_ENABLE 0x08
+#define PLX_9050_LINT2_POL 0x10
+#define PLX_9050_LINT2_STATUS 0x20
+#define PLX_9050_INTR_ENABLE 0x40
+#define PLX_9050_SW_INTR 0x80
+
+/* Masks to access the init_ctrl PLX register */
+#define PC300_CLKSEL_MASK (0x00000004UL)
+#define PC300_CHMEDIA_MASK(chan) (0x00000020UL<<(chan*3))
+#define PC300_CTYPE_MASK (0x00000800UL)
+
+/* CPLD Registers (base addr = falcbase, TE only) */
+/* CPLD v. 0 */
+#define CPLD_REG1 0x140 /* Chip resets, DCD/CTS status */
+#define CPLD_REG2 0x144 /* Clock enable , LED control */
+/* CPLD v. 2 or higher */
+#define CPLD_V2_REG1 0x100 /* Chip resets, DCD/CTS status */
+#define CPLD_V2_REG2 0x104 /* Clock enable , LED control */
+#define CPLD_ID_REG 0x108 /* CPLD version */
+
+/* CPLD Register bit description: for the FALC bits, they should always be
+ set based on the channel (use (bit<<(2*ch)) to access the correct bit for
+ that channel) */
+#define CPLD_REG1_FALC_RESET 0x01
+#define CPLD_REG1_SCA_RESET 0x02
+#define CPLD_REG1_GLOBAL_CLK 0x08
+#define CPLD_REG1_FALC_DCD 0x10
+#define CPLD_REG1_FALC_CTS 0x20
+
+#define CPLD_REG2_FALC_TX_CLK 0x01
+#define CPLD_REG2_FALC_RX_CLK 0x02
+#define CPLD_REG2_FALC_LED1 0x10
+#define CPLD_REG2_FALC_LED2 0x20
+
+/* Structure with FALC-related fields (TE only) */
+#define PC300_FALC_MAXLOOP 0x0000ffff /* for falc_issue_cmd() */
+
+typedef struct falc {
+ ucchar sync; /* If true FALC is synchronized */
+ ucchar active; /* if TRUE then already active */
+ ucchar loop_active; /* if TRUE a line loopback UP was received */
+ ucchar loop_gen; /* if TRUE a line loopback UP was issued */
+
+ ucchar num_channels;
+ ucchar offset; /* 1 for T1, 0 for E1 */
+ ucchar full_bandwidth;
+
+ ucchar xmb_cause;
+ ucchar multiframe_mode;
+
+ /* Statistics */
+ ucshort pden; /* Pulse Density violation count */
+ ucshort los; /* Loss of Signal count */
+ ucshort losr; /* Loss of Signal recovery count */
+ ucshort lfa; /* Loss of frame alignment count */
+ ucshort farec; /* Frame Alignment Recovery count */
+ ucshort lmfa; /* Loss of multiframe alignment count */
+ ucshort ais; /* Remote Alarm indication Signal count */
+ ucshort sec; /* One-second timer */
+ ucshort es; /* Errored second */
+ ucshort rai; /* remote alarm received */
+ ucshort bec;
+ ucshort fec;
+ ucshort cvc;
+ ucshort cec;
+ ucshort ebc;
+
+ /* Status */
+ ucchar red_alarm;
+ ucchar blue_alarm;
+ ucchar loss_fa;
+ ucchar yellow_alarm;
+ ucchar loss_mfa;
+ ucchar prbs;
+} falc_t;
+
+typedef struct falc_status {
+ ucchar sync; /* If true FALC is synchronized */
+ ucchar red_alarm;
+ ucchar blue_alarm;
+ ucchar loss_fa;
+ ucchar yellow_alarm;
+ ucchar loss_mfa;
+ ucchar prbs;
+} falc_status_t;
+
+typedef struct rsv_x21_status {
+ ucchar dcd;
+ ucchar dsr;
+ ucchar cts;
+ ucchar rts;
+ ucchar dtr;
+} rsv_x21_status_t;
+
+typedef struct pc300stats {
+ int hw_type;
+ uclong line_on;
+ uclong line_off;
+ struct net_device_stats gen_stats;
+ falc_t te_stats;
+} pc300stats_t;
+
+typedef struct pc300status {
+ int hw_type;
+ rsv_x21_status_t gen_status;
+ falc_status_t te_status;
+} pc300status_t;
+
+typedef struct pc300loopback {
+ char loop_type;
+ char loop_on;
+} pc300loopback_t;
+
+typedef struct pc300patterntst {
+ char patrntst_on; /* 0 - off; 1 - on; 2 - read num_errors */
+ ucshort num_errors;
+} pc300patterntst_t;
+
+typedef struct pc300dev {
+ void *if_ptr; /* General purpose pointer */
+ struct pc300ch *chan;
+ ucchar trace_on;
+ uclong line_on; /* DCD(X.21, RSV) / sync(TE) change counters */
+ uclong line_off;
+#ifdef __KERNEL__
+ char name[16];
+ struct net_device *dev;
+
+ void *private;
+ struct sk_buff *tx_skb;
+ union { /* This union has all the protocol-specific structures */
+ struct ppp_device pppdev;
+ }ifu;
+#ifdef CONFIG_PC300_MLPPP
+ void *cpc_tty; /* information to PC300 TTY driver */
+#endif
+#endif /* __KERNEL__ */
+}pc300dev_t;
+
+typedef struct pc300hw {
+ int type; /* RSV, X21, etc. */
+ int bus; /* Bus (PCI, PMC, etc.) */
+ int nchan; /* number of channels */
+ int irq; /* interrupt request level */
+ uclong clock; /* Board clock */
+ ucchar cpld_id; /* CPLD ID (TE only) */
+ ucshort cpld_reg1; /* CPLD reg 1 (TE only) */
+ ucshort cpld_reg2; /* CPLD reg 2 (TE only) */
+ ucshort gpioc_reg; /* PLX GPIOC reg */
+ ucshort intctl_reg; /* PLX Int Ctrl/Status reg */
+ uclong iophys; /* PLX registers I/O base */
+ uclong iosize; /* PLX registers I/O size */
+ uclong plxphys; /* PLX registers MMIO base (physical) */
+ void __iomem * plxbase; /* PLX registers MMIO base (virtual) */
+ uclong plxsize; /* PLX registers MMIO size */
+ uclong scaphys; /* SCA registers MMIO base (physical) */
+ void __iomem * scabase; /* SCA registers MMIO base (virtual) */
+ uclong scasize; /* SCA registers MMIO size */
+ uclong ramphys; /* On-board RAM MMIO base (physical) */
+ void __iomem * rambase; /* On-board RAM MMIO base (virtual) */
+ uclong alloc_ramsize; /* RAM MMIO size allocated by the PCI bridge */
+ uclong ramsize; /* On-board RAM MMIO size */
+ uclong falcphys; /* FALC registers MMIO base (physical) */
+ void __iomem * falcbase;/* FALC registers MMIO base (virtual) */
+ uclong falcsize; /* FALC registers MMIO size */
+} pc300hw_t;
+
+typedef struct pc300chconf {
+ sync_serial_settings phys_settings; /* Clock type/rate (in bps),
+ loopback mode */
+ raw_hdlc_proto proto_settings; /* Encoding, parity (CRC) */
+ uclong media; /* HW media (RS232, V.35, etc.) */
+ uclong proto; /* Protocol (PPP, X.25, etc.) */
+ ucchar monitor; /* Monitor mode (0 = off, !0 = on) */
+
+ /* TE-specific parameters */
+ ucchar lcode; /* Line Code (AMI, B8ZS, etc.) */
+ ucchar fr_mode; /* Frame Mode (ESF, D4, etc.) */
+ ucchar lbo; /* Line Build Out */
+ ucchar rx_sens; /* Rx Sensitivity (long- or short-haul) */
+ uclong tslot_bitmap; /* bit[i]=1 => timeslot _i_ is active */
+} pc300chconf_t;
+
+typedef struct pc300ch {
+ struct pc300 *card;
+ int channel;
+ pc300dev_t d;
+ pc300chconf_t conf;
+ ucchar tx_first_bd; /* First TX DMA block descr. w/ data */
+ ucchar tx_next_bd; /* Next free TX DMA block descriptor */
+ ucchar rx_first_bd; /* First free RX DMA block descriptor */
+ ucchar rx_last_bd; /* Last free RX DMA block descriptor */
+ ucchar nfree_tx_bd; /* Number of free TX DMA block descriptors */
+ falc_t falc; /* FALC structure (TE only) */
+} pc300ch_t;
+
+typedef struct pc300 {
+ pc300hw_t hw; /* hardware config. */
+ pc300ch_t chan[PC300_MAXCHAN];
+#ifdef __KERNEL__
+ spinlock_t card_lock;
+#endif /* __KERNEL__ */
+} pc300_t;
+
+typedef struct pc300conf {
+ pc300hw_t hw;
+ pc300chconf_t conf;
+} pc300conf_t;
+
+/* DEV ioctl() commands */
+#define N_SPPP_IOCTLS 2
+
+enum pc300_ioctl_cmds {
+ SIOCCPCRESERVED = (SIOCDEVPRIVATE + N_SPPP_IOCTLS),
+ SIOCGPC300CONF,
+ SIOCSPC300CONF,
+ SIOCGPC300STATUS,
+ SIOCGPC300FALCSTATUS,
+ SIOCGPC300UTILSTATS,
+ SIOCGPC300UTILSTATUS,
+ SIOCSPC300TRACE,
+ SIOCSPC300LOOPBACK,
+ SIOCSPC300PATTERNTEST,
+};
+
+/* Loopback types - PC300/TE boards */
+enum pc300_loopback_cmds {
+ PC300LOCLOOP = 1,
+ PC300REMLOOP,
+ PC300PAYLOADLOOP,
+ PC300GENLOOPUP,
+ PC300GENLOOPDOWN,
+};
+
+/* Control Constant Definitions */
+#define PC300_RSV 0x01
+#define PC300_X21 0x02
+#define PC300_TE 0x03
+
+#define PC300_PCI 0x00
+#define PC300_PMC 0x01
+
+#define PC300_LC_AMI 0x01
+#define PC300_LC_B8ZS 0x02
+#define PC300_LC_NRZ 0x03
+#define PC300_LC_HDB3 0x04
+
+/* Framing (T1) */
+#define PC300_FR_ESF 0x01
+#define PC300_FR_D4 0x02
+#define PC300_FR_ESF_JAPAN 0x03
+
+/* Framing (E1) */
+#define PC300_FR_MF_CRC4 0x04
+#define PC300_FR_MF_NON_CRC4 0x05
+#define PC300_FR_UNFRAMED 0x06
+
+#define PC300_LBO_0_DB 0x00
+#define PC300_LBO_7_5_DB 0x01
+#define PC300_LBO_15_DB 0x02
+#define PC300_LBO_22_5_DB 0x03
+
+#define PC300_RX_SENS_SH 0x01
+#define PC300_RX_SENS_LH 0x02
+
+#define PC300_TX_TIMEOUT (2*HZ)
+#define PC300_TX_QUEUE_LEN 100
+#define PC300_DEF_MTU 1600
+
+#ifdef __KERNEL__
+/* Function Prototypes */
+int dma_buf_write(pc300_t *, int, ucchar *, int);
+int dma_buf_read(pc300_t *, int, struct sk_buff *);
+void tx_dma_start(pc300_t *, int);
+void rx_dma_start(pc300_t *, int);
+void tx_dma_stop(pc300_t *, int);
+void rx_dma_stop(pc300_t *, int);
+int cpc_queue_xmit(struct sk_buff *, struct net_device *);
+void cpc_net_rx(struct net_device *);
+void cpc_sca_status(pc300_t *, int);
+int cpc_change_mtu(struct net_device *, int);
+int cpc_ioctl(struct net_device *, struct ifreq *, int);
+int ch_config(pc300dev_t *);
+int rx_config(pc300dev_t *);
+int tx_config(pc300dev_t *);
+void cpc_opench(pc300dev_t *);
+void cpc_closech(pc300dev_t *);
+int cpc_open(struct net_device *dev);
+int cpc_close(struct net_device *dev);
+int cpc_set_media(hdlc_device *, int);
+#endif /* __KERNEL__ */
+
+#endif /* _PC300_H */
+
diff --git a/drivers/net/wan/pc300_drv.c b/drivers/net/wan/pc300_drv.c
new file mode 100644
index 00000000000..d67be2587d4
--- /dev/null
+++ b/drivers/net/wan/pc300_drv.c
@@ -0,0 +1,3692 @@
+#define USE_PCI_CLOCK
+static char rcsid[] =
+"Revision: 3.4.5 Date: 2002/03/07 ";
+
+/*
+ * pc300.c Cyclades-PC300(tm) Driver.
+ *
+ * Author: Ivan Passos <ivan@cyclades.com>
+ * Maintainer: PC300 Maintainer <pc300@cyclades.com>
+ *
+ * Copyright: (c) 1999-2003 Cyclades Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Using tabstop = 4.
+ *
+ * $Log: pc300_drv.c,v $
+ * Revision 3.23 2002/03/20 13:58:40 henrique
+ * Fixed ortographic mistakes
+ *
+ * Revision 3.22 2002/03/13 16:56:56 henrique
+ * Take out the debug messages
+ *
+ * Revision 3.21 2002/03/07 14:17:09 henrique
+ * License data fixed
+ *
+ * Revision 3.20 2002/01/17 17:58:52 ivan
+ * Support for PC300-TE/M (PMC).
+ *
+ * Revision 3.19 2002/01/03 17:08:47 daniela
+ * Enables DMA reception when the SCA-II disables it improperly.
+ *
+ * Revision 3.18 2001/12/03 18:47:50 daniela
+ * Esthetic changes.
+ *
+ * Revision 3.17 2001/10/19 16:50:13 henrique
+ * Patch to kernel 2.4.12 and new generic hdlc.
+ *
+ * Revision 3.16 2001/10/16 15:12:31 regina
+ * clear statistics
+ *
+ * Revision 3.11 to 3.15 2001/10/11 20:26:04 daniela
+ * More DMA fixes for noisy lines.
+ * Return the size of bad frames in dma_get_rx_frame_size, so that the Rx buffer
+ * descriptors can be cleaned by dma_buf_read (called in cpc_net_rx).
+ * Renamed dma_start routine to rx_dma_start. Improved Rx statistics.
+ * Fixed BOF interrupt treatment. Created dma_start routine.
+ * Changed min and max to cpc_min and cpc_max.
+ *
+ * Revision 3.10 2001/08/06 12:01:51 regina
+ * Fixed problem in DSR_DE bit.
+ *
+ * Revision 3.9 2001/07/18 19:27:26 daniela
+ * Added some history comments.
+ *
+ * Revision 3.8 2001/07/12 13:11:19 regina
+ * bug fix - DCD-OFF in pc300 tty driver
+ *
+ * Revision 3.3 to 3.7 2001/07/06 15:00:20 daniela
+ * Removing kernel 2.4.3 and previous support.
+ * DMA transmission bug fix.
+ * MTU check in cpc_net_rx fixed.
+ * Boot messages reviewed.
+ * New configuration parameters (line code, CRC calculation and clock).
+ *
+ * Revision 3.2 2001/06/22 13:13:02 regina
+ * MLPPP implementation. Changed the header of message trace to include
+ * the device name. New format : "hdlcX[R/T]: ".
+ * Default configuration changed.
+ *
+ * Revision 3.1 2001/06/15 regina
+ * in cpc_queue_xmit, netif_stop_queue is called if don't have free descriptor
+ * upping major version number
+ *
+ * Revision 1.1.1.1 2001/06/13 20:25:04 daniela
+ * PC300 initial CVS version (3.4.0-pre1)
+ *
+ * Revision 3.0.1.2 2001/06/08 daniela
+ * Did some changes in the DMA programming implementation to avoid the
+ * occurrence of a SCA-II bug when CDA is accessed during a DMA transfer.
+ *
+ * Revision 3.0.1.1 2001/05/02 daniela
+ * Added kernel 2.4.3 support.
+ *
+ * Revision 3.0.1.0 2001/03/13 daniela, henrique
+ * Added Frame Relay Support.
+ * Driver now uses HDLC generic driver to provide protocol support.
+ *
+ * Revision 3.0.0.8 2001/03/02 daniela
+ * Fixed ram size detection.
+ * Changed SIOCGPC300CONF ioctl, to give hw information to pc300util.
+ *
+ * Revision 3.0.0.7 2001/02/23 daniela
+ * netif_stop_queue called before the SCA-II transmition commands in
+ * cpc_queue_xmit, and with interrupts disabled to avoid race conditions with
+ * transmition interrupts.
+ * Fixed falc_check_status for Unframed E1.
+ *
+ * Revision 3.0.0.6 2000/12/13 daniela
+ * Implemented pc300util support: trace, statistics, status and loopback
+ * tests for the PC300 TE boards.
+ *
+ * Revision 3.0.0.5 2000/12/12 ivan
+ * Added support for Unframed E1.
+ * Implemented monitor mode.
+ * Fixed DCD sensitivity on the second channel.
+ * Driver now complies with new PCI kernel architecture.
+ *
+ * Revision 3.0.0.4 2000/09/28 ivan
+ * Implemented DCD sensitivity.
+ * Moved hardware-specific open to the end of cpc_open, to avoid race
+ * conditions with early reception interrupts.
+ * Included code for [request|release]_mem_region().
+ * Changed location of pc300.h .
+ * Minor code revision (contrib. of Jeff Garzik).
+ *
+ * Revision 3.0.0.3 2000/07/03 ivan
+ * Previous bugfix for the framing errors with external clock made X21
+ * boards stop working. This version fixes it.
+ *
+ * Revision 3.0.0.2 2000/06/23 ivan
+ * Revisited cpc_queue_xmit to prevent race conditions on Tx DMA buffer
+ * handling when Tx timeouts occur.
+ * Revisited Rx statistics.
+ * Fixed a bug in the SCA-II programming that would cause framing errors
+ * when external clock was configured.
+ *
+ * Revision 3.0.0.1 2000/05/26 ivan
+ * Added logic in the SCA interrupt handler so that no board can monopolize
+ * the driver.
+ * Request PLX I/O region, although driver doesn't use it, to avoid
+ * problems with other drivers accessing it.
+ *
+ * Revision 3.0.0.0 2000/05/15 ivan
+ * Did some changes in the DMA programming implementation to avoid the
+ * occurrence of a SCA-II bug in the second channel.
+ * Implemented workaround for PLX9050 bug that would cause a system lockup
+ * in certain systems, depending on the MMIO addresses allocated to the
+ * board.
+ * Fixed the FALC chip programming to avoid synchronization problems in the
+ * second channel (TE only).
+ * Implemented a cleaner and faster Tx DMA descriptor cleanup procedure in
+ * cpc_queue_xmit().
+ * Changed the built-in driver implementation so that the driver can use the
+ * general 'hdlcN' naming convention instead of proprietary device names.
+ * Driver load messages are now device-centric, instead of board-centric.
+ * Dynamic allocation of net_device structures.
+ * Code is now compliant with the new module interface (module_[init|exit]).
+ * Make use of the PCI helper functions to access PCI resources.
+ *
+ * Revision 2.0.0.0 2000/04/15 ivan
+ * Added support for the PC300/TE boards (T1/FT1/E1/FE1).
+ *
+ * Revision 1.1.0.0 2000/02/28 ivan
+ * Major changes in the driver architecture.
+ * Softnet compliancy implemented.
+ * Driver now reports physical instead of virtual memory addresses.
+ * Added cpc_change_mtu function.
+ *
+ * Revision 1.0.0.0 1999/12/16 ivan
+ * First official release.
+ * Support for 1- and 2-channel boards (which use distinct PCI Device ID's).
+ * Support for monolythic installation (i.e., drv built into the kernel).
+ * X.25 additional checking when lapb_[dis]connect_request returns an error.
+ * SCA programming now covers X.21 as well.
+ *
+ * Revision 0.3.1.0 1999/11/18 ivan
+ * Made X.25 support configuration-dependent (as it depends on external
+ * modules to work).
+ * Changed X.25-specific function names to comply with adopted convention.
+ * Fixed typos in X.25 functions that would cause compile errors (Daniela).
+ * Fixed bug in ch_config that would disable interrupts on a previously
+ * enabled channel if the other channel on the same board was enabled later.
+ *
+ * Revision 0.3.0.0 1999/11/16 daniela
+ * X.25 support.
+ *
+ * Revision 0.2.3.0 1999/11/15 ivan
+ * Function cpc_ch_status now provides more detailed information.
+ * Added support for X.21 clock configuration.
+ * Changed TNR1 setting in order to prevent Tx FIFO overaccesses by the SCA.
+ * Now using PCI clock instead of internal oscillator clock for the SCA.
+ *
+ * Revision 0.2.2.0 1999/11/10 ivan
+ * Changed the *_dma_buf_check functions so that they would print only
+ * the useful info instead of the whole buffer descriptor bank.
+ * Fixed bug in cpc_queue_xmit that would eventually crash the system
+ * in case of a packet drop.
+ * Implemented TX underrun handling.
+ * Improved SCA fine tuning to boost up its performance.
+ *
+ * Revision 0.2.1.0 1999/11/03 ivan
+ * Added functions *dma_buf_pt_init to allow independent initialization
+ * of the next-descr. and DMA buffer pointers on the DMA descriptors.
+ * Kernel buffer release and tbusy clearing is now done in the interrupt
+ * handler.
+ * Fixed bug in cpc_open that would cause an interface reopen to fail.
+ * Added a protocol-specific code section in cpc_net_rx.
+ * Removed printk level defs (they might be added back after the beta phase).
+ *
+ * Revision 0.2.0.0 1999/10/28 ivan
+ * Revisited the code so that new protocols can be easily added / supported.
+ *
+ * Revision 0.1.0.1 1999/10/20 ivan
+ * Mostly "esthetic" changes.
+ *
+ * Revision 0.1.0.0 1999/10/11 ivan
+ * Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/if.h>
+
+#include <net/syncppp.h>
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "pc300.h"
+
+#define CPC_LOCK(card,flags) \
+ do { \
+ spin_lock_irqsave(&card->card_lock, flags); \
+ } while (0)
+
+#define CPC_UNLOCK(card,flags) \
+ do { \
+ spin_unlock_irqrestore(&card->card_lock, flags); \
+ } while (0)
+
+#undef PC300_DEBUG_PCI
+#undef PC300_DEBUG_INTR
+#undef PC300_DEBUG_TX
+#undef PC300_DEBUG_RX
+#undef PC300_DEBUG_OTHER
+
+static struct pci_device_id cpc_pci_dev_id[] __devinitdata = {
+ /* PC300/RSV or PC300/X21, 2 chan */
+ {0x120e, 0x300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x300},
+ /* PC300/RSV or PC300/X21, 1 chan */
+ {0x120e, 0x301, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x301},
+ /* PC300/TE, 2 chan */
+ {0x120e, 0x310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x310},
+ /* PC300/TE, 1 chan */
+ {0x120e, 0x311, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x311},
+ /* PC300/TE-M, 2 chan */
+ {0x120e, 0x320, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x320},
+ /* PC300/TE-M, 1 chan */
+ {0x120e, 0x321, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0x321},
+ /* End of table */
+ {0,},
+};
+MODULE_DEVICE_TABLE(pci, cpc_pci_dev_id);
+
+#ifndef cpc_min
+#define cpc_min(a,b) (((a)<(b))?(a):(b))
+#endif
+#ifndef cpc_max
+#define cpc_max(a,b) (((a)>(b))?(a):(b))
+#endif
+
+/* prototypes */
+static void tx_dma_buf_pt_init(pc300_t *, int);
+static void tx_dma_buf_init(pc300_t *, int);
+static void rx_dma_buf_pt_init(pc300_t *, int);
+static void rx_dma_buf_init(pc300_t *, int);
+static void tx_dma_buf_check(pc300_t *, int);
+static void rx_dma_buf_check(pc300_t *, int);
+static irqreturn_t cpc_intr(int, void *, struct pt_regs *);
+static struct net_device_stats *cpc_get_stats(struct net_device *);
+static int clock_rate_calc(uclong, uclong, int *);
+static uclong detect_ram(pc300_t *);
+static void plx_init(pc300_t *);
+static void cpc_trace(struct net_device *, struct sk_buff *, char);
+static int cpc_attach(struct net_device *, unsigned short, unsigned short);
+
+#ifdef CONFIG_PC300_MLPPP
+void cpc_tty_init(pc300dev_t * dev);
+void cpc_tty_unregister_service(pc300dev_t * pc300dev);
+void cpc_tty_receive(pc300dev_t * pc300dev);
+void cpc_tty_trigger_poll(pc300dev_t * pc300dev);
+void cpc_tty_reset_var(void);
+#endif
+
+/************************/
+/*** DMA Routines ***/
+/************************/
+static void tx_dma_buf_pt_init(pc300_t * card, int ch)
+{
+ int i;
+ int ch_factor = ch * N_DMA_TX_BUF;
+ volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+ + DMA_TX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+ for (i = 0; i < N_DMA_TX_BUF; i++, ptdescr++) {
+ cpc_writel(&ptdescr->next, (uclong) (DMA_TX_BD_BASE +
+ (ch_factor + ((i + 1) & (N_DMA_TX_BUF - 1))) * sizeof(pcsca_bd_t)));
+ cpc_writel(&ptdescr->ptbuf,
+ (uclong) (DMA_TX_BASE + (ch_factor + i) * BD_DEF_LEN));
+ }
+}
+
+static void tx_dma_buf_init(pc300_t * card, int ch)
+{
+ int i;
+ int ch_factor = ch * N_DMA_TX_BUF;
+ volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+ + DMA_TX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+ for (i = 0; i < N_DMA_TX_BUF; i++, ptdescr++) {
+ memset_io(ptdescr, 0, sizeof(pcsca_bd_t));
+ cpc_writew(&ptdescr->len, 0);
+ cpc_writeb(&ptdescr->status, DST_OSB);
+ }
+ tx_dma_buf_pt_init(card, ch);
+}
+
+static void rx_dma_buf_pt_init(pc300_t * card, int ch)
+{
+ int i;
+ int ch_factor = ch * N_DMA_RX_BUF;
+ volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+ + DMA_RX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+ for (i = 0; i < N_DMA_RX_BUF; i++, ptdescr++) {
+ cpc_writel(&ptdescr->next, (uclong) (DMA_RX_BD_BASE +
+ (ch_factor + ((i + 1) & (N_DMA_RX_BUF - 1))) * sizeof(pcsca_bd_t)));
+ cpc_writel(&ptdescr->ptbuf,
+ (uclong) (DMA_RX_BASE + (ch_factor + i) * BD_DEF_LEN));
+ }
+}
+
+static void rx_dma_buf_init(pc300_t * card, int ch)
+{
+ int i;
+ int ch_factor = ch * N_DMA_RX_BUF;
+ volatile pcsca_bd_t __iomem *ptdescr = (card->hw.rambase
+ + DMA_RX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+
+ for (i = 0; i < N_DMA_RX_BUF; i++, ptdescr++) {
+ memset_io(ptdescr, 0, sizeof(pcsca_bd_t));
+ cpc_writew(&ptdescr->len, 0);
+ cpc_writeb(&ptdescr->status, 0);
+ }
+ rx_dma_buf_pt_init(card, ch);
+}
+
+static void tx_dma_buf_check(pc300_t * card, int ch)
+{
+ volatile pcsca_bd_t __iomem *ptdescr;
+ int i;
+ ucshort first_bd = card->chan[ch].tx_first_bd;
+ ucshort next_bd = card->chan[ch].tx_next_bd;
+
+ printk("#CH%d: f_bd = %d(0x%08zx), n_bd = %d(0x%08zx)\n", ch,
+ first_bd, TX_BD_ADDR(ch, first_bd),
+ next_bd, TX_BD_ADDR(ch, next_bd));
+ for (i = first_bd,
+ ptdescr = (card->hw.rambase + TX_BD_ADDR(ch, first_bd));
+ i != ((next_bd + 1) & (N_DMA_TX_BUF - 1));
+ i = (i + 1) & (N_DMA_TX_BUF - 1),
+ ptdescr = (card->hw.rambase + TX_BD_ADDR(ch, i))) {
+ printk("\n CH%d TX%d: next=0x%x, ptbuf=0x%x, ST=0x%x, len=%d",
+ ch, i, cpc_readl(&ptdescr->next),
+ cpc_readl(&ptdescr->ptbuf),
+ cpc_readb(&ptdescr->status), cpc_readw(&ptdescr->len));
+ }
+ printk("\n");
+}
+
+#ifdef PC300_DEBUG_OTHER
+/* Show all TX buffer descriptors */
+static void tx1_dma_buf_check(pc300_t * card, int ch)
+{
+ volatile pcsca_bd_t __iomem *ptdescr;
+ int i;
+ ucshort first_bd = card->chan[ch].tx_first_bd;
+ ucshort next_bd = card->chan[ch].tx_next_bd;
+ uclong scabase = card->hw.scabase;
+
+ printk ("\nnfree_tx_bd = %d \n", card->chan[ch].nfree_tx_bd);
+ printk("#CH%d: f_bd = %d(0x%08x), n_bd = %d(0x%08x)\n", ch,
+ first_bd, TX_BD_ADDR(ch, first_bd),
+ next_bd, TX_BD_ADDR(ch, next_bd));
+ printk("TX_CDA=0x%08x, TX_EDA=0x%08x\n",
+ cpc_readl(scabase + DTX_REG(CDAL, ch)),
+ cpc_readl(scabase + DTX_REG(EDAL, ch)));
+ for (i = 0; i < N_DMA_TX_BUF; i++) {
+ ptdescr = (card->hw.rambase + TX_BD_ADDR(ch, i));
+ printk("\n CH%d TX%d: next=0x%x, ptbuf=0x%x, ST=0x%x, len=%d",
+ ch, i, cpc_readl(&ptdescr->next),
+ cpc_readl(&ptdescr->ptbuf),
+ cpc_readb(&ptdescr->status), cpc_readw(&ptdescr->len));
+ }
+ printk("\n");
+}
+#endif
+
+static void rx_dma_buf_check(pc300_t * card, int ch)
+{
+ volatile pcsca_bd_t __iomem *ptdescr;
+ int i;
+ ucshort first_bd = card->chan[ch].rx_first_bd;
+ ucshort last_bd = card->chan[ch].rx_last_bd;
+ int ch_factor;
+
+ ch_factor = ch * N_DMA_RX_BUF;
+ printk("#CH%d: f_bd = %d, l_bd = %d\n", ch, first_bd, last_bd);
+ for (i = 0, ptdescr = (card->hw.rambase +
+ DMA_RX_BD_BASE + ch_factor * sizeof(pcsca_bd_t));
+ i < N_DMA_RX_BUF; i++, ptdescr++) {
+ if (cpc_readb(&ptdescr->status) & DST_OSB)
+ printk ("\n CH%d RX%d: next=0x%x, ptbuf=0x%x, ST=0x%x, len=%d",
+ ch, i, cpc_readl(&ptdescr->next),
+ cpc_readl(&ptdescr->ptbuf),
+ cpc_readb(&ptdescr->status),
+ cpc_readw(&ptdescr->len));
+ }
+ printk("\n");
+}
+
+int dma_get_rx_frame_size(pc300_t * card, int ch)
+{
+ volatile pcsca_bd_t __iomem *ptdescr;
+ ucshort first_bd = card->chan[ch].rx_first_bd;
+ int rcvd = 0;
+ volatile ucchar status;
+
+ ptdescr = (card->hw.rambase + RX_BD_ADDR(ch, first_bd));
+ while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+ rcvd += cpc_readw(&ptdescr->len);
+ first_bd = (first_bd + 1) & (N_DMA_RX_BUF - 1);
+ if ((status & DST_EOM) || (first_bd == card->chan[ch].rx_last_bd)) {
+ /* Return the size of a good frame or incomplete bad frame
+ * (dma_buf_read will clean the buffer descriptors in this case). */
+ return (rcvd);
+ }
+ ptdescr = (card->hw.rambase + cpc_readl(&ptdescr->next));
+ }
+ return (-1);
+}
+
+/*
+ * dma_buf_write: writes a frame to the Tx DMA buffers
+ * NOTE: this function writes one frame at a time.
+ */
+int dma_buf_write(pc300_t * card, int ch, ucchar * ptdata, int len)
+{
+ int i, nchar;
+ volatile pcsca_bd_t __iomem *ptdescr;
+ int tosend = len;
+ ucchar nbuf = ((len - 1) / BD_DEF_LEN) + 1;
+
+ if (nbuf >= card->chan[ch].nfree_tx_bd) {
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < nbuf; i++) {
+ ptdescr = (card->hw.rambase +
+ TX_BD_ADDR(ch, card->chan[ch].tx_next_bd));
+ nchar = cpc_min(BD_DEF_LEN, tosend);
+ if (cpc_readb(&ptdescr->status) & DST_OSB) {
+ memcpy_toio((card->hw.rambase + cpc_readl(&ptdescr->ptbuf)),
+ &ptdata[len - tosend], nchar);
+ cpc_writew(&ptdescr->len, nchar);
+ card->chan[ch].nfree_tx_bd--;
+ if ((i + 1) == nbuf) {
+ /* This must be the last BD to be used */
+ cpc_writeb(&ptdescr->status, DST_EOM);
+ } else {
+ cpc_writeb(&ptdescr->status, 0);
+ }
+ } else {
+ return -ENOMEM;
+ }
+ tosend -= nchar;
+ card->chan[ch].tx_next_bd =
+ (card->chan[ch].tx_next_bd + 1) & (N_DMA_TX_BUF - 1);
+ }
+ /* If it gets to here, it means we have sent the whole frame */
+ return 0;
+}
+
+/*
+ * dma_buf_read: reads a frame from the Rx DMA buffers
+ * NOTE: this function reads one frame at a time.
+ */
+int dma_buf_read(pc300_t * card, int ch, struct sk_buff *skb)
+{
+ int nchar;
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ volatile pcsca_bd_t __iomem *ptdescr;
+ int rcvd = 0;
+ volatile ucchar status;
+
+ ptdescr = (card->hw.rambase +
+ RX_BD_ADDR(ch, chan->rx_first_bd));
+ while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+ nchar = cpc_readw(&ptdescr->len);
+ if ((status & (DST_OVR | DST_CRC | DST_RBIT | DST_SHRT | DST_ABT))
+ || (nchar > BD_DEF_LEN)) {
+
+ if (nchar > BD_DEF_LEN)
+ status |= DST_RBIT;
+ rcvd = -status;
+ /* Discard remaining descriptors used by the bad frame */
+ while (chan->rx_first_bd != chan->rx_last_bd) {
+ cpc_writeb(&ptdescr->status, 0);
+ chan->rx_first_bd = (chan->rx_first_bd+1) & (N_DMA_RX_BUF-1);
+ if (status & DST_EOM)
+ break;
+ ptdescr = (card->hw.rambase +
+ cpc_readl(&ptdescr->next));
+ status = cpc_readb(&ptdescr->status);
+ }
+ break;
+ }
+ if (nchar != 0) {
+ if (skb) {
+ memcpy_fromio(skb_put(skb, nchar),
+ (card->hw.rambase+cpc_readl(&ptdescr->ptbuf)),nchar);
+ }
+ rcvd += nchar;
+ }
+ cpc_writeb(&ptdescr->status, 0);
+ cpc_writeb(&ptdescr->len, 0);
+ chan->rx_first_bd = (chan->rx_first_bd + 1) & (N_DMA_RX_BUF - 1);
+
+ if (status & DST_EOM)
+ break;
+
+ ptdescr = (card->hw.rambase + cpc_readl(&ptdescr->next));
+ }
+
+ if (rcvd != 0) {
+ /* Update pointer */
+ chan->rx_last_bd = (chan->rx_first_bd - 1) & (N_DMA_RX_BUF - 1);
+ /* Update EDA */
+ cpc_writel(card->hw.scabase + DRX_REG(EDAL, ch),
+ RX_BD_ADDR(ch, chan->rx_last_bd));
+ }
+ return (rcvd);
+}
+
+void tx_dma_stop(pc300_t * card, int ch)
+{
+ void __iomem *scabase = card->hw.scabase;
+ ucchar drr_ena_bit = 1 << (5 + 2 * ch);
+ ucchar drr_rst_bit = 1 << (1 + 2 * ch);
+
+ /* Disable DMA */
+ cpc_writeb(scabase + DRR, drr_ena_bit);
+ cpc_writeb(scabase + DRR, drr_rst_bit & ~drr_ena_bit);
+}
+
+void rx_dma_stop(pc300_t * card, int ch)
+{
+ void __iomem *scabase = card->hw.scabase;
+ ucchar drr_ena_bit = 1 << (4 + 2 * ch);
+ ucchar drr_rst_bit = 1 << (2 * ch);
+
+ /* Disable DMA */
+ cpc_writeb(scabase + DRR, drr_ena_bit);
+ cpc_writeb(scabase + DRR, drr_rst_bit & ~drr_ena_bit);
+}
+
+void rx_dma_start(pc300_t * card, int ch)
+{
+ void __iomem *scabase = card->hw.scabase;
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+
+ /* Start DMA */
+ cpc_writel(scabase + DRX_REG(CDAL, ch),
+ RX_BD_ADDR(ch, chan->rx_first_bd));
+ if (cpc_readl(scabase + DRX_REG(CDAL,ch)) !=
+ RX_BD_ADDR(ch, chan->rx_first_bd)) {
+ cpc_writel(scabase + DRX_REG(CDAL, ch),
+ RX_BD_ADDR(ch, chan->rx_first_bd));
+ }
+ cpc_writel(scabase + DRX_REG(EDAL, ch),
+ RX_BD_ADDR(ch, chan->rx_last_bd));
+ cpc_writew(scabase + DRX_REG(BFLL, ch), BD_DEF_LEN);
+ cpc_writeb(scabase + DSR_RX(ch), DSR_DE);
+ if (!(cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+ cpc_writeb(scabase + DSR_RX(ch), DSR_DE);
+ }
+}
+
+/*************************/
+/*** FALC Routines ***/
+/*************************/
+void falc_issue_cmd(pc300_t * card, int ch, ucchar cmd)
+{
+ void __iomem *falcbase = card->hw.falcbase;
+ unsigned long i = 0;
+
+ while (cpc_readb(falcbase + F_REG(SIS, ch)) & SIS_CEC) {
+ if (i++ >= PC300_FALC_MAXLOOP) {
+ printk("%s: FALC command locked(cmd=0x%x).\n",
+ card->chan[ch].d.name, cmd);
+ break;
+ }
+ }
+ cpc_writeb(falcbase + F_REG(CMDR, ch), cmd);
+}
+
+void falc_intr_enable(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ /* Interrupt pins are open-drain */
+ cpc_writeb(falcbase + F_REG(IPC, ch),
+ cpc_readb(falcbase + F_REG(IPC, ch)) & ~IPC_IC0);
+ /* Conters updated each second */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_ECM);
+ /* Enable SEC and ES interrupts */
+ cpc_writeb(falcbase + F_REG(IMR3, ch),
+ cpc_readb(falcbase + F_REG(IMR3, ch)) & ~(IMR3_SEC | IMR3_ES));
+ if (conf->fr_mode == PC300_FR_UNFRAMED) {
+ cpc_writeb(falcbase + F_REG(IMR4, ch),
+ cpc_readb(falcbase + F_REG(IMR4, ch)) & ~(IMR4_LOS));
+ } else {
+ cpc_writeb(falcbase + F_REG(IMR4, ch),
+ cpc_readb(falcbase + F_REG(IMR4, ch)) &
+ ~(IMR4_LFA | IMR4_AIS | IMR4_LOS | IMR4_SLIP));
+ }
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(IMR3, ch),
+ cpc_readb(falcbase + F_REG(IMR3, ch)) & ~IMR3_LLBSC);
+ } else {
+ cpc_writeb(falcbase + F_REG(IPC, ch),
+ cpc_readb(falcbase + F_REG(IPC, ch)) | IPC_SCI);
+ if (conf->fr_mode == PC300_FR_UNFRAMED) {
+ cpc_writeb(falcbase + F_REG(IMR2, ch),
+ cpc_readb(falcbase + F_REG(IMR2, ch)) & ~(IMR2_LOS));
+ } else {
+ cpc_writeb(falcbase + F_REG(IMR2, ch),
+ cpc_readb(falcbase + F_REG(IMR2, ch)) &
+ ~(IMR2_FAR | IMR2_LFA | IMR2_AIS | IMR2_LOS));
+ if (pfalc->multiframe_mode) {
+ cpc_writeb(falcbase + F_REG(IMR2, ch),
+ cpc_readb(falcbase + F_REG(IMR2, ch)) &
+ ~(IMR2_T400MS | IMR2_MFAR));
+ } else {
+ cpc_writeb(falcbase + F_REG(IMR2, ch),
+ cpc_readb(falcbase + F_REG(IMR2, ch)) |
+ IMR2_T400MS | IMR2_MFAR);
+ }
+ }
+ }
+}
+
+void falc_open_timeslot(pc300_t * card, int ch, int timeslot)
+{
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar tshf = card->chan[ch].falc.offset;
+
+ cpc_writeb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch),
+ cpc_readb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch)) &
+ ~(0x80 >> ((timeslot - tshf) & 0x07)));
+ cpc_writeb(falcbase + F_REG((TTR1 + timeslot / 8), ch),
+ cpc_readb(falcbase + F_REG((TTR1 + timeslot / 8), ch)) |
+ (0x80 >> (timeslot & 0x07)));
+ cpc_writeb(falcbase + F_REG((RTR1 + timeslot / 8), ch),
+ cpc_readb(falcbase + F_REG((RTR1 + timeslot / 8), ch)) |
+ (0x80 >> (timeslot & 0x07)));
+}
+
+void falc_close_timeslot(pc300_t * card, int ch, int timeslot)
+{
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar tshf = card->chan[ch].falc.offset;
+
+ cpc_writeb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch),
+ cpc_readb(falcbase + F_REG((ICB1 + (timeslot - tshf) / 8), ch)) |
+ (0x80 >> ((timeslot - tshf) & 0x07)));
+ cpc_writeb(falcbase + F_REG((TTR1 + timeslot / 8), ch),
+ cpc_readb(falcbase + F_REG((TTR1 + timeslot / 8), ch)) &
+ ~(0x80 >> (timeslot & 0x07)));
+ cpc_writeb(falcbase + F_REG((RTR1 + timeslot / 8), ch),
+ cpc_readb(falcbase + F_REG((RTR1 + timeslot / 8), ch)) &
+ ~(0x80 >> (timeslot & 0x07)));
+}
+
+void falc_close_all_timeslots(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ cpc_writeb(falcbase + F_REG(ICB1, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(TTR1, ch), 0);
+ cpc_writeb(falcbase + F_REG(RTR1, ch), 0);
+ cpc_writeb(falcbase + F_REG(ICB2, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(TTR2, ch), 0);
+ cpc_writeb(falcbase + F_REG(RTR2, ch), 0);
+ cpc_writeb(falcbase + F_REG(ICB3, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(TTR3, ch), 0);
+ cpc_writeb(falcbase + F_REG(RTR3, ch), 0);
+ if (conf->media == IF_IFACE_E1) {
+ cpc_writeb(falcbase + F_REG(ICB4, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(TTR4, ch), 0);
+ cpc_writeb(falcbase + F_REG(RTR4, ch), 0);
+ }
+}
+
+void falc_open_all_timeslots(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ cpc_writeb(falcbase + F_REG(ICB1, ch), 0);
+ if (conf->fr_mode == PC300_FR_UNFRAMED) {
+ cpc_writeb(falcbase + F_REG(TTR1, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RTR1, ch), 0xff);
+ } else {
+ /* Timeslot 0 is never enabled */
+ cpc_writeb(falcbase + F_REG(TTR1, ch), 0x7f);
+ cpc_writeb(falcbase + F_REG(RTR1, ch), 0x7f);
+ }
+ cpc_writeb(falcbase + F_REG(ICB2, ch), 0);
+ cpc_writeb(falcbase + F_REG(TTR2, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RTR2, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(ICB3, ch), 0);
+ cpc_writeb(falcbase + F_REG(TTR3, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RTR3, ch), 0xff);
+ if (conf->media == IF_IFACE_E1) {
+ cpc_writeb(falcbase + F_REG(ICB4, ch), 0);
+ cpc_writeb(falcbase + F_REG(TTR4, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RTR4, ch), 0xff);
+ } else {
+ cpc_writeb(falcbase + F_REG(ICB4, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(TTR4, ch), 0x80);
+ cpc_writeb(falcbase + F_REG(RTR4, ch), 0x80);
+ }
+}
+
+void falc_init_timeslot(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ int tslot;
+
+ for (tslot = 0; tslot < pfalc->num_channels; tslot++) {
+ if (conf->tslot_bitmap & (1 << tslot)) {
+ // Channel enabled
+ falc_open_timeslot(card, ch, tslot + 1);
+ } else {
+ // Channel disabled
+ falc_close_timeslot(card, ch, tslot + 1);
+ }
+ }
+}
+
+void falc_enable_comm(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+
+ if (pfalc->full_bandwidth) {
+ falc_open_all_timeslots(card, ch);
+ } else {
+ falc_init_timeslot(card, ch);
+ }
+ // CTS/DCD ON
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) &
+ ~((CPLD_REG1_FALC_DCD | CPLD_REG1_FALC_CTS) << (2 * ch)));
+}
+
+void falc_disable_comm(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+
+ if (pfalc->loop_active != 2) {
+ falc_close_all_timeslots(card, ch);
+ }
+ // CTS/DCD OFF
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+ ((CPLD_REG1_FALC_DCD | CPLD_REG1_FALC_CTS) << (2 * ch)));
+}
+
+void falc_init_t1(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar dja = (ch ? (LIM2_DJA2 | LIM2_DJA1) : 0);
+
+ /* Switch to T1 mode (PCM 24) */
+ cpc_writeb(falcbase + F_REG(FMR1, ch), FMR1_PMOD);
+
+ /* Wait 20 us for setup */
+ udelay(20);
+
+ /* Transmit Buffer Size (1 frame) */
+ cpc_writeb(falcbase + F_REG(SIC1, ch), SIC1_XBS0);
+
+ /* Clock mode */
+ if (conf->phys_settings.clock_type == CLOCK_INT) { /* Master mode */
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_MAS);
+ } else { /* Slave mode */
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_MAS);
+ cpc_writeb(falcbase + F_REG(LOOP, ch),
+ cpc_readb(falcbase + F_REG(LOOP, ch)) & ~LOOP_RTM);
+ }
+
+ cpc_writeb(falcbase + F_REG(IPC, ch), IPC_SCI);
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) &
+ ~(FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1));
+
+ switch (conf->lcode) {
+ case PC300_LC_AMI:
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) |
+ FMR0_XC1 | FMR0_RC1);
+ /* Clear Channel register to ON for all channels */
+ cpc_writeb(falcbase + F_REG(CCB1, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(CCB2, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(CCB3, ch), 0xff);
+ break;
+
+ case PC300_LC_B8ZS:
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) |
+ FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1);
+ break;
+
+ case PC300_LC_NRZ:
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) | 0x00);
+ break;
+ }
+
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_ELOS);
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) & ~(LIM0_SCL1 | LIM0_SCL0));
+ /* Set interface mode to 2 MBPS */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_IMOD);
+
+ switch (conf->fr_mode) {
+ case PC300_FR_ESF:
+ pfalc->multiframe_mode = 0;
+ cpc_writeb(falcbase + F_REG(FMR4, ch),
+ cpc_readb(falcbase + F_REG(FMR4, ch)) | FMR4_FM1);
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) |
+ FMR1_CRC | FMR1_EDL);
+ cpc_writeb(falcbase + F_REG(XDL1, ch), 0);
+ cpc_writeb(falcbase + F_REG(XDL2, ch), 0);
+ cpc_writeb(falcbase + F_REG(XDL3, ch), 0);
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) & ~FMR0_SRAF);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2,ch)) | FMR2_MCSP | FMR2_SSP);
+ break;
+
+ case PC300_FR_D4:
+ pfalc->multiframe_mode = 1;
+ cpc_writeb(falcbase + F_REG(FMR4, ch),
+ cpc_readb(falcbase + F_REG(FMR4, ch)) &
+ ~(FMR4_FM1 | FMR4_FM0));
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) | FMR0_SRAF);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_SSP);
+ break;
+ }
+
+ /* Enable Automatic Resynchronization */
+ cpc_writeb(falcbase + F_REG(FMR4, ch),
+ cpc_readb(falcbase + F_REG(FMR4, ch)) | FMR4_AUTO);
+
+ /* Transmit Automatic Remote Alarm */
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_AXRA);
+
+ /* Channel translation mode 1 : one to one */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_CTM);
+
+ /* No signaling */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_SIGM);
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) &
+ ~(FMR5_EIBR | FMR5_SRS));
+ cpc_writeb(falcbase + F_REG(CCR1, ch), 0);
+
+ cpc_writeb(falcbase + F_REG(LIM1, ch),
+ cpc_readb(falcbase + F_REG(LIM1, ch)) | LIM1_RIL0 | LIM1_RIL1);
+
+ switch (conf->lbo) {
+ /* Provides proper Line Build Out */
+ case PC300_LBO_0_DB:
+ cpc_writeb(falcbase + F_REG(LIM2, ch), (LIM2_LOS1 | dja));
+ cpc_writeb(falcbase + F_REG(XPM0, ch), 0x5a);
+ cpc_writeb(falcbase + F_REG(XPM1, ch), 0x8f);
+ cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+ break;
+ case PC300_LBO_7_5_DB:
+ cpc_writeb(falcbase + F_REG(LIM2, ch), (0x40 | LIM2_LOS1 | dja));
+ cpc_writeb(falcbase + F_REG(XPM0, ch), 0x11);
+ cpc_writeb(falcbase + F_REG(XPM1, ch), 0x02);
+ cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+ break;
+ case PC300_LBO_15_DB:
+ cpc_writeb(falcbase + F_REG(LIM2, ch), (0x80 | LIM2_LOS1 | dja));
+ cpc_writeb(falcbase + F_REG(XPM0, ch), 0x8e);
+ cpc_writeb(falcbase + F_REG(XPM1, ch), 0x01);
+ cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+ break;
+ case PC300_LBO_22_5_DB:
+ cpc_writeb(falcbase + F_REG(LIM2, ch), (0xc0 | LIM2_LOS1 | dja));
+ cpc_writeb(falcbase + F_REG(XPM0, ch), 0x09);
+ cpc_writeb(falcbase + F_REG(XPM1, ch), 0x01);
+ cpc_writeb(falcbase + F_REG(XPM2, ch), 0x20);
+ break;
+ }
+
+ /* Transmit Clock-Slot Offset */
+ cpc_writeb(falcbase + F_REG(XC0, ch),
+ cpc_readb(falcbase + F_REG(XC0, ch)) | 0x01);
+ /* Transmit Time-slot Offset */
+ cpc_writeb(falcbase + F_REG(XC1, ch), 0x3e);
+ /* Receive Clock-Slot offset */
+ cpc_writeb(falcbase + F_REG(RC0, ch), 0x05);
+ /* Receive Time-slot offset */
+ cpc_writeb(falcbase + F_REG(RC1, ch), 0x00);
+
+ /* LOS Detection after 176 consecutive 0s */
+ cpc_writeb(falcbase + F_REG(PCDR, ch), 0x0a);
+ /* LOS Recovery after 22 ones in the time window of PCD */
+ cpc_writeb(falcbase + F_REG(PCRR, ch), 0x15);
+
+ cpc_writeb(falcbase + F_REG(IDLE, ch), 0x7f);
+
+ if (conf->fr_mode == PC300_FR_ESF_JAPAN) {
+ cpc_writeb(falcbase + F_REG(RC1, ch),
+ cpc_readb(falcbase + F_REG(RC1, ch)) | 0x80);
+ }
+
+ falc_close_all_timeslots(card, ch);
+}
+
+void falc_init_e1(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar dja = (ch ? (LIM2_DJA2 | LIM2_DJA1) : 0);
+
+ /* Switch to E1 mode (PCM 30) */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_PMOD);
+
+ /* Clock mode */
+ if (conf->phys_settings.clock_type == CLOCK_INT) { /* Master mode */
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_MAS);
+ } else { /* Slave mode */
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_MAS);
+ }
+ cpc_writeb(falcbase + F_REG(LOOP, ch),
+ cpc_readb(falcbase + F_REG(LOOP, ch)) & ~LOOP_SFM);
+
+ cpc_writeb(falcbase + F_REG(IPC, ch), IPC_SCI);
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) &
+ ~(FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1));
+
+ switch (conf->lcode) {
+ case PC300_LC_AMI:
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) |
+ FMR0_XC1 | FMR0_RC1);
+ break;
+
+ case PC300_LC_HDB3:
+ cpc_writeb(falcbase + F_REG(FMR0, ch),
+ cpc_readb(falcbase + F_REG(FMR0, ch)) |
+ FMR0_XC0 | FMR0_XC1 | FMR0_RC0 | FMR0_RC1);
+ break;
+
+ case PC300_LC_NRZ:
+ break;
+ }
+
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) & ~(LIM0_SCL1 | LIM0_SCL0));
+ /* Set interface mode to 2 MBPS */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_IMOD);
+
+ cpc_writeb(falcbase + F_REG(XPM0, ch), 0x18);
+ cpc_writeb(falcbase + F_REG(XPM1, ch), 0x03);
+ cpc_writeb(falcbase + F_REG(XPM2, ch), 0x00);
+
+ switch (conf->fr_mode) {
+ case PC300_FR_MF_CRC4:
+ pfalc->multiframe_mode = 1;
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_XFS);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_RFS1);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_RFS0);
+ cpc_writeb(falcbase + F_REG(FMR3, ch),
+ cpc_readb(falcbase + F_REG(FMR3, ch)) & ~FMR3_EXTIW);
+
+ /* MultiFrame Resynchronization */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_MFCS);
+
+ /* Automatic Loss of Multiframe > 914 CRC errors */
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_ALMF);
+
+ /* S1 and SI1/SI2 spare Bits set to 1 */
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch)) & ~XSP_AXS);
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_EBP);
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_XS13 | XSP_XS15);
+
+ /* Automatic Force Resynchronization */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_AFR);
+
+ /* Transmit Automatic Remote Alarm */
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_AXRA);
+
+ /* Transmit Spare Bits for National Use (Y, Sn, Sa) */
+ cpc_writeb(falcbase + F_REG(XSW, ch),
+ cpc_readb(falcbase + F_REG(XSW, ch)) |
+ XSW_XY0 | XSW_XY1 | XSW_XY2 | XSW_XY3 | XSW_XY4);
+ break;
+
+ case PC300_FR_MF_NON_CRC4:
+ case PC300_FR_D4:
+ pfalc->multiframe_mode = 0;
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_XFS);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) &
+ ~(FMR2_RFS1 | FMR2_RFS0));
+ cpc_writeb(falcbase + F_REG(XSW, ch),
+ cpc_readb(falcbase + F_REG(XSW, ch)) | XSW_XSIS);
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_XSIF);
+
+ /* Automatic Force Resynchronization */
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) | FMR1_AFR);
+
+ /* Transmit Automatic Remote Alarm */
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_AXRA);
+
+ /* Transmit Spare Bits for National Use (Y, Sn, Sa) */
+ cpc_writeb(falcbase + F_REG(XSW, ch),
+ cpc_readb(falcbase + F_REG(XSW, ch)) |
+ XSW_XY0 | XSW_XY1 | XSW_XY2 | XSW_XY3 | XSW_XY4);
+ break;
+
+ case PC300_FR_UNFRAMED:
+ pfalc->multiframe_mode = 0;
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_XFS);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) &
+ ~(FMR2_RFS1 | FMR2_RFS0));
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch)) | XSP_TT0);
+ cpc_writeb(falcbase + F_REG(XSW, ch),
+ cpc_readb(falcbase + F_REG(XSW, ch)) &
+ ~(XSW_XTM|XSW_XY0|XSW_XY1|XSW_XY2|XSW_XY3|XSW_XY4));
+ cpc_writeb(falcbase + F_REG(TSWM, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) |
+ (FMR2_RTM | FMR2_DAIS));
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_AXRA);
+ cpc_writeb(falcbase + F_REG(FMR1, ch),
+ cpc_readb(falcbase + F_REG(FMR1, ch)) & ~FMR1_AFR);
+ pfalc->sync = 1;
+ cpc_writeb(falcbase + card->hw.cpld_reg2,
+ cpc_readb(falcbase + card->hw.cpld_reg2) |
+ (CPLD_REG2_FALC_LED2 << (2 * ch)));
+ break;
+ }
+
+ /* No signaling */
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch)) & ~XSP_CASEN);
+ cpc_writeb(falcbase + F_REG(CCR1, ch), 0);
+
+ cpc_writeb(falcbase + F_REG(LIM1, ch),
+ cpc_readb(falcbase + F_REG(LIM1, ch)) | LIM1_RIL0 | LIM1_RIL1);
+ cpc_writeb(falcbase + F_REG(LIM2, ch), (LIM2_LOS1 | dja));
+
+ /* Transmit Clock-Slot Offset */
+ cpc_writeb(falcbase + F_REG(XC0, ch),
+ cpc_readb(falcbase + F_REG(XC0, ch)) | 0x01);
+ /* Transmit Time-slot Offset */
+ cpc_writeb(falcbase + F_REG(XC1, ch), 0x3e);
+ /* Receive Clock-Slot offset */
+ cpc_writeb(falcbase + F_REG(RC0, ch), 0x05);
+ /* Receive Time-slot offset */
+ cpc_writeb(falcbase + F_REG(RC1, ch), 0x00);
+
+ /* LOS Detection after 176 consecutive 0s */
+ cpc_writeb(falcbase + F_REG(PCDR, ch), 0x0a);
+ /* LOS Recovery after 22 ones in the time window of PCD */
+ cpc_writeb(falcbase + F_REG(PCRR, ch), 0x15);
+
+ cpc_writeb(falcbase + F_REG(IDLE, ch), 0x7f);
+
+ falc_close_all_timeslots(card, ch);
+}
+
+void falc_init_hdlc(pc300_t * card, int ch)
+{
+ void __iomem *falcbase = card->hw.falcbase;
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+
+ /* Enable transparent data transfer */
+ if (conf->fr_mode == PC300_FR_UNFRAMED) {
+ cpc_writeb(falcbase + F_REG(MODE, ch), 0);
+ } else {
+ cpc_writeb(falcbase + F_REG(MODE, ch),
+ cpc_readb(falcbase + F_REG(MODE, ch)) |
+ (MODE_HRAC | MODE_MDS2));
+ cpc_writeb(falcbase + F_REG(RAH2, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RAH1, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RAL2, ch), 0xff);
+ cpc_writeb(falcbase + F_REG(RAL1, ch), 0xff);
+ }
+
+ /* Tx/Rx reset */
+ falc_issue_cmd(card, ch, CMDR_RRES | CMDR_XRES | CMDR_SRES);
+
+ /* Enable interrupt sources */
+ falc_intr_enable(card, ch);
+}
+
+void te_config(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar dummy;
+ unsigned long flags;
+
+ memset(pfalc, 0, sizeof(falc_t));
+ switch (conf->media) {
+ case IF_IFACE_T1:
+ pfalc->num_channels = NUM_OF_T1_CHANNELS;
+ pfalc->offset = 1;
+ break;
+ case IF_IFACE_E1:
+ pfalc->num_channels = NUM_OF_E1_CHANNELS;
+ pfalc->offset = 0;
+ break;
+ }
+ if (conf->tslot_bitmap == 0xffffffffUL)
+ pfalc->full_bandwidth = 1;
+ else
+ pfalc->full_bandwidth = 0;
+
+ CPC_LOCK(card, flags);
+ /* Reset the FALC chip */
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+ (CPLD_REG1_FALC_RESET << (2 * ch)));
+ udelay(10000);
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) &
+ ~(CPLD_REG1_FALC_RESET << (2 * ch)));
+
+ if (conf->media == IF_IFACE_T1) {
+ falc_init_t1(card, ch);
+ } else {
+ falc_init_e1(card, ch);
+ }
+ falc_init_hdlc(card, ch);
+ if (conf->rx_sens == PC300_RX_SENS_SH) {
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_EQON);
+ } else {
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_EQON);
+ }
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) |
+ ((CPLD_REG2_FALC_TX_CLK | CPLD_REG2_FALC_RX_CLK) << (2 * ch)));
+
+ /* Clear all interrupt registers */
+ dummy = cpc_readb(falcbase + F_REG(FISR0, ch)) +
+ cpc_readb(falcbase + F_REG(FISR1, ch)) +
+ cpc_readb(falcbase + F_REG(FISR2, ch)) +
+ cpc_readb(falcbase + F_REG(FISR3, ch));
+ CPC_UNLOCK(card, flags);
+}
+
+void falc_check_status(pc300_t * card, int ch, unsigned char frs0)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ /* Verify LOS */
+ if (frs0 & FRS0_LOS) {
+ if (!pfalc->red_alarm) {
+ pfalc->red_alarm = 1;
+ pfalc->los++;
+ if (!pfalc->blue_alarm) {
+ // EVENT_FALC_ABNORMAL
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise interfere
+ * with other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch))
+ | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_FALC_ABNORMAL
+ }
+ }
+ } else {
+ if (pfalc->red_alarm) {
+ pfalc->red_alarm = 0;
+ pfalc->losr++;
+ }
+ }
+
+ if (conf->fr_mode != PC300_FR_UNFRAMED) {
+ /* Verify AIS alarm */
+ if (frs0 & FRS0_AIS) {
+ if (!pfalc->blue_alarm) {
+ pfalc->blue_alarm = 1;
+ pfalc->ais++;
+ // EVENT_AIS
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise interfere with other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_AIS
+ }
+ } else {
+ pfalc->blue_alarm = 0;
+ }
+
+ /* Verify LFA */
+ if (frs0 & FRS0_LFA) {
+ if (!pfalc->loss_fa) {
+ pfalc->loss_fa = 1;
+ pfalc->lfa++;
+ if (!pfalc->blue_alarm && !pfalc->red_alarm) {
+ // EVENT_FALC_ABNORMAL
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise
+ * interfere with other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch))
+ | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_FALC_ABNORMAL
+ }
+ }
+ } else {
+ if (pfalc->loss_fa) {
+ pfalc->loss_fa = 0;
+ pfalc->farec++;
+ }
+ }
+
+ /* Verify LMFA */
+ if (pfalc->multiframe_mode && (frs0 & FRS0_LMFA)) {
+ /* D4 or CRC4 frame mode */
+ if (!pfalc->loss_mfa) {
+ pfalc->loss_mfa = 1;
+ pfalc->lmfa++;
+ if (!pfalc->blue_alarm && !pfalc->red_alarm &&
+ !pfalc->loss_fa) {
+ // EVENT_FALC_ABNORMAL
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise
+ * interfere with other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch))
+ | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_FALC_ABNORMAL
+ }
+ }
+ } else {
+ pfalc->loss_mfa = 0;
+ }
+
+ /* Verify Remote Alarm */
+ if (frs0 & FRS0_RRA) {
+ if (!pfalc->yellow_alarm) {
+ pfalc->yellow_alarm = 1;
+ pfalc->rai++;
+ if (pfalc->sync) {
+ // EVENT_RAI
+ falc_disable_comm(card, ch);
+ // EVENT_RAI
+ }
+ }
+ } else {
+ pfalc->yellow_alarm = 0;
+ }
+ } /* if !PC300_UNFRAMED */
+
+ if (pfalc->red_alarm || pfalc->loss_fa ||
+ pfalc->loss_mfa || pfalc->blue_alarm) {
+ if (pfalc->sync) {
+ pfalc->sync = 0;
+ chan->d.line_off++;
+ cpc_writeb(falcbase + card->hw.cpld_reg2,
+ cpc_readb(falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+ }
+ } else {
+ if (!pfalc->sync) {
+ pfalc->sync = 1;
+ chan->d.line_on++;
+ cpc_writeb(falcbase + card->hw.cpld_reg2,
+ cpc_readb(falcbase + card->hw.cpld_reg2) |
+ (CPLD_REG2_FALC_LED2 << (2 * ch)));
+ }
+ }
+
+ if (pfalc->sync && !pfalc->yellow_alarm) {
+ if (!pfalc->active) {
+ // EVENT_FALC_NORMAL
+ if (pfalc->loop_active) {
+ return;
+ }
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch)) & ~IMR0_PDEN);
+ }
+ falc_enable_comm(card, ch);
+ // EVENT_FALC_NORMAL
+ pfalc->active = 1;
+ }
+ } else {
+ if (pfalc->active) {
+ pfalc->active = 0;
+ }
+ }
+}
+
+void falc_update_stats(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+ ucshort counter;
+
+ counter = cpc_readb(falcbase + F_REG(FECL, ch));
+ counter |= cpc_readb(falcbase + F_REG(FECH, ch)) << 8;
+ pfalc->fec += counter;
+
+ counter = cpc_readb(falcbase + F_REG(CVCL, ch));
+ counter |= cpc_readb(falcbase + F_REG(CVCH, ch)) << 8;
+ pfalc->cvc += counter;
+
+ counter = cpc_readb(falcbase + F_REG(CECL, ch));
+ counter |= cpc_readb(falcbase + F_REG(CECH, ch)) << 8;
+ pfalc->cec += counter;
+
+ counter = cpc_readb(falcbase + F_REG(EBCL, ch));
+ counter |= cpc_readb(falcbase + F_REG(EBCH, ch)) << 8;
+ pfalc->ebc += counter;
+
+ if (cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_EPRM) {
+ mdelay(10);
+ counter = cpc_readb(falcbase + F_REG(BECL, ch));
+ counter |= cpc_readb(falcbase + F_REG(BECH, ch)) << 8;
+ pfalc->bec += counter;
+
+ if (((conf->media == IF_IFACE_T1) &&
+ (cpc_readb(falcbase + F_REG(FRS1, ch)) & FRS1_LLBAD) &&
+ (!(cpc_readb(falcbase + F_REG(FRS1, ch)) & FRS1_PDEN)))
+ ||
+ ((conf->media == IF_IFACE_E1) &&
+ (cpc_readb(falcbase + F_REG(RSP, ch)) & RSP_LLBAD))) {
+ pfalc->prbs = 2;
+ } else {
+ pfalc->prbs = 1;
+ }
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * falc_remote_loop
+ *----------------------------------------------------------------------------
+ * Description: In the remote loopback mode the clock and data recovered
+ * from the line inputs RL1/2 or RDIP/RDIN are routed back
+ * to the line outputs XL1/2 or XDOP/XDON via the analog
+ * transmitter. As in normal mode they are processsed by
+ * the synchronizer and then sent to the system interface.
+ *----------------------------------------------------------------------------
+ */
+void falc_remote_loop(pc300_t * card, int ch, int loop_on)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (loop_on) {
+ // EVENT_FALC_ABNORMAL
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise interfere with
+ * other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_FALC_ABNORMAL
+ cpc_writeb(falcbase + F_REG(LIM1, ch),
+ cpc_readb(falcbase + F_REG(LIM1, ch)) | LIM1_RL);
+ pfalc->loop_active = 1;
+ } else {
+ cpc_writeb(falcbase + F_REG(LIM1, ch),
+ cpc_readb(falcbase + F_REG(LIM1, ch)) & ~LIM1_RL);
+ pfalc->sync = 0;
+ cpc_writeb(falcbase + card->hw.cpld_reg2,
+ cpc_readb(falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+ pfalc->active = 0;
+ falc_issue_cmd(card, ch, CMDR_XRES);
+ pfalc->loop_active = 0;
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * falc_local_loop
+ *----------------------------------------------------------------------------
+ * Description: The local loopback mode disconnects the receive lines
+ * RL1/RL2 resp. RDIP/RDIN from the receiver. Instead of the
+ * signals coming from the line the data provided by system
+ * interface are routed through the analog receiver back to
+ * the system interface. The unipolar bit stream will be
+ * undisturbed transmitted on the line. Receiver and transmitter
+ * coding must be identical.
+ *----------------------------------------------------------------------------
+ */
+void falc_local_loop(pc300_t * card, int ch, int loop_on)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (loop_on) {
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) | LIM0_LL);
+ pfalc->loop_active = 1;
+ } else {
+ cpc_writeb(falcbase + F_REG(LIM0, ch),
+ cpc_readb(falcbase + F_REG(LIM0, ch)) & ~LIM0_LL);
+ pfalc->loop_active = 0;
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * falc_payload_loop
+ *----------------------------------------------------------------------------
+ * Description: This routine allows to enable/disable payload loopback.
+ * When the payload loop is activated, the received 192 bits
+ * of payload data will be looped back to the transmit
+ * direction. The framing bits, CRC6 and DL bits are not
+ * looped. They are originated by the FALC-LH transmitter.
+ *----------------------------------------------------------------------------
+ */
+void falc_payload_loop(pc300_t * card, int ch, int loop_on)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (loop_on) {
+ // EVENT_FALC_ABNORMAL
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise interfere with
+ * other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_FALC_ABNORMAL
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) | FMR2_PLB);
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(FMR4, ch),
+ cpc_readb(falcbase + F_REG(FMR4, ch)) | FMR4_TM);
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) | XSP_TT0);
+ }
+ falc_open_all_timeslots(card, ch);
+ pfalc->loop_active = 2;
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR2, ch),
+ cpc_readb(falcbase + F_REG(FMR2, ch)) & ~FMR2_PLB);
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(FMR4, ch),
+ cpc_readb(falcbase + F_REG(FMR4, ch)) & ~FMR4_TM);
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) & ~XSP_TT0);
+ }
+ pfalc->sync = 0;
+ cpc_writeb(falcbase + card->hw.cpld_reg2,
+ cpc_readb(falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+ pfalc->active = 0;
+ falc_issue_cmd(card, ch, CMDR_XRES);
+ pfalc->loop_active = 0;
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * turn_off_xlu
+ *----------------------------------------------------------------------------
+ * Description: Turns XLU bit off in the proper register
+ *----------------------------------------------------------------------------
+ */
+void turn_off_xlu(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) & ~FMR5_XLU);
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR3, ch),
+ cpc_readb(falcbase + F_REG(FMR3, ch)) & ~FMR3_XLU);
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * turn_off_xld
+ *----------------------------------------------------------------------------
+ * Description: Turns XLD bit off in the proper register
+ *----------------------------------------------------------------------------
+ */
+void turn_off_xld(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) & ~FMR5_XLD);
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR3, ch),
+ cpc_readb(falcbase + F_REG(FMR3, ch)) & ~FMR3_XLD);
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * falc_generate_loop_up_code
+ *----------------------------------------------------------------------------
+ * Description: This routine writes the proper FALC chip register in order
+ * to generate a LOOP activation code over a T1/E1 line.
+ *----------------------------------------------------------------------------
+ */
+void falc_generate_loop_up_code(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) | FMR5_XLU);
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR3, ch),
+ cpc_readb(falcbase + F_REG(FMR3, ch)) | FMR3_XLU);
+ }
+ // EVENT_FALC_ABNORMAL
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable this interrupt as it may otherwise interfere with
+ * other working boards. */
+ cpc_writeb(falcbase + F_REG(IMR0, ch),
+ cpc_readb(falcbase + F_REG(IMR0, ch)) | IMR0_PDEN);
+ }
+ falc_disable_comm(card, ch);
+ // EVENT_FALC_ABNORMAL
+ pfalc->loop_gen = 1;
+}
+
+/*----------------------------------------------------------------------------
+ * falc_generate_loop_down_code
+ *----------------------------------------------------------------------------
+ * Description: This routine writes the proper FALC chip register in order
+ * to generate a LOOP deactivation code over a T1/E1 line.
+ *----------------------------------------------------------------------------
+ */
+void falc_generate_loop_down_code(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (conf->media == IF_IFACE_T1) {
+ cpc_writeb(falcbase + F_REG(FMR5, ch),
+ cpc_readb(falcbase + F_REG(FMR5, ch)) | FMR5_XLD);
+ } else {
+ cpc_writeb(falcbase + F_REG(FMR3, ch),
+ cpc_readb(falcbase + F_REG(FMR3, ch)) | FMR3_XLD);
+ }
+ pfalc->sync = 0;
+ cpc_writeb(falcbase + card->hw.cpld_reg2,
+ cpc_readb(falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED2 << (2 * ch)));
+ pfalc->active = 0;
+//? falc_issue_cmd(card, ch, CMDR_XRES);
+ pfalc->loop_gen = 0;
+}
+
+/*----------------------------------------------------------------------------
+ * falc_pattern_test
+ *----------------------------------------------------------------------------
+ * Description: This routine generates a pattern code and checks
+ * it on the reception side.
+ *----------------------------------------------------------------------------
+ */
+void falc_pattern_test(pc300_t * card, int ch, unsigned int activate)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (activate) {
+ pfalc->prbs = 1;
+ pfalc->bec = 0;
+ if (conf->media == IF_IFACE_T1) {
+ /* Disable local loop activation/deactivation detect */
+ cpc_writeb(falcbase + F_REG(IMR3, ch),
+ cpc_readb(falcbase + F_REG(IMR3, ch)) | IMR3_LLBSC);
+ } else {
+ /* Disable local loop activation/deactivation detect */
+ cpc_writeb(falcbase + F_REG(IMR1, ch),
+ cpc_readb(falcbase + F_REG(IMR1, ch)) | IMR1_LLBSC);
+ }
+ /* Activates generation and monitoring of PRBS
+ * (Pseudo Random Bit Sequence) */
+ cpc_writeb(falcbase + F_REG(LCR1, ch),
+ cpc_readb(falcbase + F_REG(LCR1, ch)) | LCR1_EPRM | LCR1_XPRBS);
+ } else {
+ pfalc->prbs = 0;
+ /* Deactivates generation and monitoring of PRBS
+ * (Pseudo Random Bit Sequence) */
+ cpc_writeb(falcbase + F_REG(LCR1, ch),
+ cpc_readb(falcbase+F_REG(LCR1,ch)) & ~(LCR1_EPRM | LCR1_XPRBS));
+ if (conf->media == IF_IFACE_T1) {
+ /* Enable local loop activation/deactivation detect */
+ cpc_writeb(falcbase + F_REG(IMR3, ch),
+ cpc_readb(falcbase + F_REG(IMR3, ch)) & ~IMR3_LLBSC);
+ } else {
+ /* Enable local loop activation/deactivation detect */
+ cpc_writeb(falcbase + F_REG(IMR1, ch),
+ cpc_readb(falcbase + F_REG(IMR1, ch)) & ~IMR1_LLBSC);
+ }
+ }
+}
+
+/*----------------------------------------------------------------------------
+ * falc_pattern_test_error
+ *----------------------------------------------------------------------------
+ * Description: This routine returns the bit error counter value
+ *----------------------------------------------------------------------------
+ */
+ucshort falc_pattern_test_error(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+
+ return (pfalc->bec);
+}
+
+/**********************************/
+/*** Net Interface Routines ***/
+/**********************************/
+
+static void
+cpc_trace(struct net_device *dev, struct sk_buff *skb_main, char rx_tx)
+{
+ struct sk_buff *skb;
+
+ if ((skb = dev_alloc_skb(10 + skb_main->len)) == NULL) {
+ printk("%s: out of memory\n", dev->name);
+ return;
+ }
+ skb_put(skb, 10 + skb_main->len);
+
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_CUST);
+ skb->mac.raw = skb->data;
+ skb->pkt_type = PACKET_HOST;
+ skb->len = 10 + skb_main->len;
+
+ memcpy(skb->data, dev->name, 5);
+ skb->data[5] = '[';
+ skb->data[6] = rx_tx;
+ skb->data[7] = ']';
+ skb->data[8] = ':';
+ skb->data[9] = ' ';
+ memcpy(&skb->data[10], skb_main->data, skb_main->len);
+
+ netif_rx(skb);
+}
+
+void cpc_tx_timeout(struct net_device *dev)
+{
+ pc300dev_t *d = (pc300dev_t *) dev->priv;
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ struct net_device_stats *stats = hdlc_stats(dev);
+ int ch = chan->channel;
+ unsigned long flags;
+ ucchar ilar;
+
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+ CPC_LOCK(card, flags);
+ if ((ilar = cpc_readb(card->hw.scabase + ILAR)) != 0) {
+ printk("%s: ILAR=0x%x\n", dev->name, ilar);
+ cpc_writeb(card->hw.scabase + ILAR, ilar);
+ cpc_writeb(card->hw.scabase + DMER, 0x80);
+ }
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ dev->trans_start = jiffies;
+ CPC_UNLOCK(card, flags);
+ netif_wake_queue(dev);
+}
+
+int cpc_queue_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ pc300dev_t *d = (pc300dev_t *) dev->priv;
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ struct net_device_stats *stats = hdlc_stats(dev);
+ int ch = chan->channel;
+ unsigned long flags;
+#ifdef PC300_DEBUG_TX
+ int i;
+#endif
+
+ if (chan->conf.monitor) {
+ /* In monitor mode no Tx is done: ignore packet */
+ dev_kfree_skb(skb);
+ return 0;
+ } else if (!netif_carrier_ok(dev)) {
+ /* DCD must be OFF: drop packet */
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ stats->tx_carrier_errors++;
+ return 0;
+ } else if (cpc_readb(card->hw.scabase + M_REG(ST3, ch)) & ST3_DCD) {
+ printk("%s: DCD is OFF. Going administrative down.\n", dev->name);
+ stats->tx_errors++;
+ stats->tx_carrier_errors++;
+ dev_kfree_skb(skb);
+ netif_carrier_off(dev);
+ CPC_LOCK(card, flags);
+ cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_BUF_CLR);
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ CPC_UNLOCK(card, flags);
+ netif_wake_queue(dev);
+ return 0;
+ }
+
+ /* Write buffer to DMA buffers */
+ if (dma_buf_write(card, ch, (ucchar *) skb->data, skb->len) != 0) {
+// printk("%s: write error. Dropping TX packet.\n", dev->name);
+ netif_stop_queue(dev);
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ stats->tx_dropped++;
+ return 0;
+ }
+#ifdef PC300_DEBUG_TX
+ printk("%s T:", dev->name);
+ for (i = 0; i < skb->len; i++)
+ printk(" %02x", *(skb->data + i));
+ printk("\n");
+#endif
+
+ if (d->trace_on) {
+ cpc_trace(dev, skb, 'T');
+ }
+ dev->trans_start = jiffies;
+
+ /* Start transmission */
+ CPC_LOCK(card, flags);
+ /* verify if it has more than one free descriptor */
+ if (card->chan[ch].nfree_tx_bd <= 1) {
+ /* don't have so stop the queue */
+ netif_stop_queue(dev);
+ }
+ cpc_writel(card->hw.scabase + DTX_REG(EDAL, ch),
+ TX_BD_ADDR(ch, chan->tx_next_bd));
+ cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_ENA);
+ cpc_writeb(card->hw.scabase + DSR_TX(ch), DSR_DE);
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) |
+ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ CPC_UNLOCK(card, flags);
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+void cpc_net_rx(struct net_device *dev)
+{
+ pc300dev_t *d = (pc300dev_t *) dev->priv;
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ struct net_device_stats *stats = hdlc_stats(dev);
+ int ch = chan->channel;
+#ifdef PC300_DEBUG_RX
+ int i;
+#endif
+ int rxb;
+ struct sk_buff *skb;
+
+ while (1) {
+ if ((rxb = dma_get_rx_frame_size(card, ch)) == -1)
+ return;
+
+ if (!netif_carrier_ok(dev)) {
+ /* DCD must be OFF: drop packet */
+ printk("%s : DCD is OFF - drop %d rx bytes\n", dev->name, rxb);
+ skb = NULL;
+ } else {
+ if (rxb > (dev->mtu + 40)) { /* add headers */
+ printk("%s : MTU exceeded %d\n", dev->name, rxb);
+ skb = NULL;
+ } else {
+ skb = dev_alloc_skb(rxb);
+ if (skb == NULL) {
+ printk("%s: Memory squeeze!!\n", dev->name);
+ return;
+ }
+ skb->dev = dev;
+ }
+ }
+
+ if (((rxb = dma_buf_read(card, ch, skb)) <= 0) || (skb == NULL)) {
+#ifdef PC300_DEBUG_RX
+ printk("%s: rxb = %x\n", dev->name, rxb);
+#endif
+ if ((skb == NULL) && (rxb > 0)) {
+ /* rxb > dev->mtu */
+ stats->rx_errors++;
+ stats->rx_length_errors++;
+ continue;
+ }
+
+ if (rxb < 0) { /* Invalid frame */
+ rxb = -rxb;
+ if (rxb & DST_OVR) {
+ stats->rx_errors++;
+ stats->rx_fifo_errors++;
+ }
+ if (rxb & DST_CRC) {
+ stats->rx_errors++;
+ stats->rx_crc_errors++;
+ }
+ if (rxb & (DST_RBIT | DST_SHRT | DST_ABT)) {
+ stats->rx_errors++;
+ stats->rx_frame_errors++;
+ }
+ }
+ if (skb) {
+ dev_kfree_skb_irq(skb);
+ }
+ continue;
+ }
+
+ stats->rx_bytes += rxb;
+
+#ifdef PC300_DEBUG_RX
+ printk("%s R:", dev->name);
+ for (i = 0; i < skb->len; i++)
+ printk(" %02x", *(skb->data + i));
+ printk("\n");
+#endif
+ if (d->trace_on) {
+ cpc_trace(dev, skb, 'R');
+ }
+ stats->rx_packets++;
+ skb->protocol = hdlc_type_trans(skb, dev);
+ netif_rx(skb);
+ }
+}
+
+/************************************/
+/*** PC300 Interrupt Routines ***/
+/************************************/
+static void sca_tx_intr(pc300dev_t *dev)
+{
+ pc300ch_t *chan = (pc300ch_t *)dev->chan;
+ pc300_t *card = (pc300_t *)chan->card;
+ int ch = chan->channel;
+ volatile pcsca_bd_t __iomem * ptdescr;
+ struct net_device_stats *stats = hdlc_stats(dev->dev);
+
+ /* Clean up descriptors from previous transmission */
+ ptdescr = (card->hw.rambase +
+ TX_BD_ADDR(ch,chan->tx_first_bd));
+ while ((cpc_readl(card->hw.scabase + DTX_REG(CDAL,ch)) !=
+ TX_BD_ADDR(ch,chan->tx_first_bd)) &&
+ (cpc_readb(&ptdescr->status) & DST_OSB)) {
+ stats->tx_packets++;
+ stats->tx_bytes += cpc_readw(&ptdescr->len);
+ cpc_writeb(&ptdescr->status, DST_OSB);
+ cpc_writew(&ptdescr->len, 0);
+ chan->nfree_tx_bd++;
+ chan->tx_first_bd = (chan->tx_first_bd + 1) & (N_DMA_TX_BUF - 1);
+ ptdescr = (card->hw.rambase + TX_BD_ADDR(ch,chan->tx_first_bd));
+ }
+
+#ifdef CONFIG_PC300_MLPPP
+ if (chan->conf.proto == PC300_PROTO_MLPPP) {
+ cpc_tty_trigger_poll(dev);
+ } else {
+#endif
+ /* Tell the upper layer we are ready to transmit more packets */
+ netif_wake_queue(dev->dev);
+#ifdef CONFIG_PC300_MLPPP
+ }
+#endif
+}
+
+static void sca_intr(pc300_t * card)
+{
+ void __iomem *scabase = card->hw.scabase;
+ volatile uclong status;
+ int ch;
+ int intr_count = 0;
+ unsigned char dsr_rx;
+
+ while ((status = cpc_readl(scabase + ISR0)) != 0) {
+ for (ch = 0; ch < card->hw.nchan; ch++) {
+ pc300ch_t *chan = &card->chan[ch];
+ pc300dev_t *d = &chan->d;
+ struct net_device *dev = d->dev;
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+
+ spin_lock(&card->card_lock);
+
+ /**** Reception ****/
+ if (status & IR0_DRX((IR0_DMIA | IR0_DMIB), ch)) {
+ ucchar drx_stat = cpc_readb(scabase + DSR_RX(ch));
+
+ /* Clear RX interrupts */
+ cpc_writeb(scabase + DSR_RX(ch), drx_stat | DSR_DWE);
+
+#ifdef PC300_DEBUG_INTR
+ printk ("sca_intr: RX intr chan[%d] (st=0x%08lx, dsr=0x%02x)\n",
+ ch, status, drx_stat);
+#endif
+ if (status & IR0_DRX(IR0_DMIA, ch)) {
+ if (drx_stat & DSR_BOF) {
+#ifdef CONFIG_PC300_MLPPP
+ if (chan->conf.proto == PC300_PROTO_MLPPP) {
+ /* verify if driver is TTY */
+ if ((cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+ rx_dma_stop(card, ch);
+ }
+ cpc_tty_receive(d);
+ rx_dma_start(card, ch);
+ } else
+#endif
+ {
+ if ((cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+ rx_dma_stop(card, ch);
+ }
+ cpc_net_rx(dev);
+ /* Discard invalid frames */
+ hdlc->stats.rx_errors++;
+ hdlc->stats.rx_over_errors++;
+ chan->rx_first_bd = 0;
+ chan->rx_last_bd = N_DMA_RX_BUF - 1;
+ rx_dma_start(card, ch);
+ }
+ }
+ }
+ if (status & IR0_DRX(IR0_DMIB, ch)) {
+ if (drx_stat & DSR_EOM) {
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase +
+ card->hw.cpld_reg2,
+ cpc_readb (card->hw.falcbase +
+ card->hw.cpld_reg2) |
+ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+#ifdef CONFIG_PC300_MLPPP
+ if (chan->conf.proto == PC300_PROTO_MLPPP) {
+ /* verify if driver is TTY */
+ cpc_tty_receive(d);
+ } else {
+ cpc_net_rx(dev);
+ }
+#else
+ cpc_net_rx(dev);
+#endif
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase +
+ card->hw.cpld_reg2,
+ cpc_readb (card->hw.falcbase +
+ card->hw.cpld_reg2) &
+ ~ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ }
+ }
+ if (!(dsr_rx = cpc_readb(scabase + DSR_RX(ch)) & DSR_DE)) {
+#ifdef PC300_DEBUG_INTR
+ printk("%s: RX intr chan[%d] (st=0x%08lx, dsr=0x%02x, dsr2=0x%02x)\n",
+ dev->name, ch, status, drx_stat, dsr_rx);
+#endif
+ cpc_writeb(scabase + DSR_RX(ch), (dsr_rx | DSR_DE) & 0xfe);
+ }
+ }
+
+ /**** Transmission ****/
+ if (status & IR0_DTX((IR0_EFT | IR0_DMIA | IR0_DMIB), ch)) {
+ ucchar dtx_stat = cpc_readb(scabase + DSR_TX(ch));
+
+ /* Clear TX interrupts */
+ cpc_writeb(scabase + DSR_TX(ch), dtx_stat | DSR_DWE);
+
+#ifdef PC300_DEBUG_INTR
+ printk ("sca_intr: TX intr chan[%d] (st=0x%08lx, dsr=0x%02x)\n",
+ ch, status, dtx_stat);
+#endif
+ if (status & IR0_DTX(IR0_EFT, ch)) {
+ if (dtx_stat & DSR_UDRF) {
+ if (cpc_readb (scabase + M_REG(TBN, ch)) != 0) {
+ cpc_writeb(scabase + M_REG(CMD,ch), CMD_TX_BUF_CLR);
+ }
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb (card->hw.falcbase +
+ card->hw.cpld_reg2) &
+ ~ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ hdlc->stats.tx_errors++;
+ hdlc->stats.tx_fifo_errors++;
+ sca_tx_intr(d);
+ }
+ }
+ if (status & IR0_DTX(IR0_DMIA, ch)) {
+ if (dtx_stat & DSR_BOF) {
+ }
+ }
+ if (status & IR0_DTX(IR0_DMIB, ch)) {
+ if (dtx_stat & DSR_EOM) {
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb (card->hw.falcbase +
+ card->hw.cpld_reg2) &
+ ~ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ sca_tx_intr(d);
+ }
+ }
+ }
+
+ /**** MSCI ****/
+ if (status & IR0_M(IR0_RXINTA, ch)) {
+ ucchar st1 = cpc_readb(scabase + M_REG(ST1, ch));
+
+ /* Clear MSCI interrupts */
+ cpc_writeb(scabase + M_REG(ST1, ch), st1);
+
+#ifdef PC300_DEBUG_INTR
+ printk("sca_intr: MSCI intr chan[%d] (st=0x%08lx, st1=0x%02x)\n",
+ ch, status, st1);
+#endif
+ if (st1 & ST1_CDCD) { /* DCD changed */
+ if (cpc_readb(scabase + M_REG(ST3, ch)) & ST3_DCD) {
+ printk ("%s: DCD is OFF. Going administrative down.\n",
+ dev->name);
+#ifdef CONFIG_PC300_MLPPP
+ if (chan->conf.proto != PC300_PROTO_MLPPP) {
+ netif_carrier_off(dev);
+ }
+#else
+ netif_carrier_off(dev);
+
+#endif
+ card->chan[ch].d.line_off++;
+ } else { /* DCD = 1 */
+ printk ("%s: DCD is ON. Going administrative up.\n",
+ dev->name);
+#ifdef CONFIG_PC300_MLPPP
+ if (chan->conf.proto != PC300_PROTO_MLPPP)
+ /* verify if driver is not TTY */
+#endif
+ netif_carrier_on(dev);
+ card->chan[ch].d.line_on++;
+ }
+ }
+ }
+ spin_unlock(&card->card_lock);
+ }
+ if (++intr_count == 10)
+ /* Too much work at this board. Force exit */
+ break;
+ }
+}
+
+static void falc_t1_loop_detection(pc300_t * card, int ch, ucchar frs1)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_XPRBS) == 0) &&
+ !pfalc->loop_gen) {
+ if (frs1 & FRS1_LLBDD) {
+ // A Line Loop Back Deactivation signal detected
+ if (pfalc->loop_active) {
+ falc_remote_loop(card, ch, 0);
+ }
+ } else {
+ if ((frs1 & FRS1_LLBAD) &&
+ ((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_EPRM) == 0)) {
+ // A Line Loop Back Activation signal detected
+ if (!pfalc->loop_active) {
+ falc_remote_loop(card, ch, 1);
+ }
+ }
+ }
+ }
+}
+
+static void falc_e1_loop_detection(pc300_t * card, int ch, ucchar rsp)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+
+ if (((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_XPRBS) == 0) &&
+ !pfalc->loop_gen) {
+ if (rsp & RSP_LLBDD) {
+ // A Line Loop Back Deactivation signal detected
+ if (pfalc->loop_active) {
+ falc_remote_loop(card, ch, 0);
+ }
+ } else {
+ if ((rsp & RSP_LLBAD) &&
+ ((cpc_readb(falcbase + F_REG(LCR1, ch)) & LCR1_EPRM) == 0)) {
+ // A Line Loop Back Activation signal detected
+ if (!pfalc->loop_active) {
+ falc_remote_loop(card, ch, 1);
+ }
+ }
+ }
+ }
+}
+
+static void falc_t1_intr(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar isr0, isr3, gis;
+ ucchar dummy;
+
+ while ((gis = cpc_readb(falcbase + F_REG(GIS, ch))) != 0) {
+ if (gis & GIS_ISR0) {
+ isr0 = cpc_readb(falcbase + F_REG(FISR0, ch));
+ if (isr0 & FISR0_PDEN) {
+ /* Read the bit to clear the situation */
+ if (cpc_readb(falcbase + F_REG(FRS1, ch)) &
+ FRS1_PDEN) {
+ pfalc->pden++;
+ }
+ }
+ }
+
+ if (gis & GIS_ISR1) {
+ dummy = cpc_readb(falcbase + F_REG(FISR1, ch));
+ }
+
+ if (gis & GIS_ISR2) {
+ dummy = cpc_readb(falcbase + F_REG(FISR2, ch));
+ }
+
+ if (gis & GIS_ISR3) {
+ isr3 = cpc_readb(falcbase + F_REG(FISR3, ch));
+ if (isr3 & FISR3_SEC) {
+ pfalc->sec++;
+ falc_update_stats(card, ch);
+ falc_check_status(card, ch,
+ cpc_readb(falcbase + F_REG(FRS0, ch)));
+ }
+ if (isr3 & FISR3_ES) {
+ pfalc->es++;
+ }
+ if (isr3 & FISR3_LLBSC) {
+ falc_t1_loop_detection(card, ch,
+ cpc_readb(falcbase + F_REG(FRS1, ch)));
+ }
+ }
+ }
+}
+
+static void falc_e1_intr(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = (pc300ch_t *) & card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ void __iomem *falcbase = card->hw.falcbase;
+ ucchar isr1, isr2, isr3, gis, rsp;
+ ucchar dummy;
+
+ while ((gis = cpc_readb(falcbase + F_REG(GIS, ch))) != 0) {
+ rsp = cpc_readb(falcbase + F_REG(RSP, ch));
+
+ if (gis & GIS_ISR0) {
+ dummy = cpc_readb(falcbase + F_REG(FISR0, ch));
+ }
+ if (gis & GIS_ISR1) {
+ isr1 = cpc_readb(falcbase + F_REG(FISR1, ch));
+ if (isr1 & FISR1_XMB) {
+ if ((pfalc->xmb_cause & 2)
+ && pfalc->multiframe_mode) {
+ if (cpc_readb (falcbase + F_REG(FRS0, ch)) &
+ (FRS0_LOS | FRS0_AIS | FRS0_LFA)) {
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch))
+ & ~XSP_AXS);
+ } else {
+ cpc_writeb(falcbase + F_REG(XSP, ch),
+ cpc_readb(falcbase + F_REG(XSP, ch))
+ | XSP_AXS);
+ }
+ }
+ pfalc->xmb_cause = 0;
+ cpc_writeb(falcbase + F_REG(IMR1, ch),
+ cpc_readb(falcbase + F_REG(IMR1, ch)) | IMR1_XMB);
+ }
+ if (isr1 & FISR1_LLBSC) {
+ falc_e1_loop_detection(card, ch, rsp);
+ }
+ }
+ if (gis & GIS_ISR2) {
+ isr2 = cpc_readb(falcbase + F_REG(FISR2, ch));
+ if (isr2 & FISR2_T400MS) {
+ cpc_writeb(falcbase + F_REG(XSW, ch),
+ cpc_readb(falcbase + F_REG(XSW, ch)) | XSW_XRA);
+ }
+ if (isr2 & FISR2_MFAR) {
+ cpc_writeb(falcbase + F_REG(XSW, ch),
+ cpc_readb(falcbase + F_REG(XSW, ch)) & ~XSW_XRA);
+ }
+ if (isr2 & (FISR2_FAR | FISR2_LFA | FISR2_AIS | FISR2_LOS)) {
+ pfalc->xmb_cause |= 2;
+ cpc_writeb(falcbase + F_REG(IMR1, ch),
+ cpc_readb(falcbase + F_REG(IMR1, ch)) & ~IMR1_XMB);
+ }
+ }
+ if (gis & GIS_ISR3) {
+ isr3 = cpc_readb(falcbase + F_REG(FISR3, ch));
+ if (isr3 & FISR3_SEC) {
+ pfalc->sec++;
+ falc_update_stats(card, ch);
+ falc_check_status(card, ch,
+ cpc_readb(falcbase + F_REG(FRS0, ch)));
+ }
+ if (isr3 & FISR3_ES) {
+ pfalc->es++;
+ }
+ }
+ }
+}
+
+static void falc_intr(pc300_t * card)
+{
+ int ch;
+
+ for (ch = 0; ch < card->hw.nchan; ch++) {
+ pc300ch_t *chan = &card->chan[ch];
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+
+ if (conf->media == IF_IFACE_T1) {
+ falc_t1_intr(card, ch);
+ } else {
+ falc_e1_intr(card, ch);
+ }
+ }
+}
+
+static irqreturn_t cpc_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+ pc300_t *card;
+ volatile ucchar plx_status;
+
+ if ((card = (pc300_t *) dev_id) == 0) {
+#ifdef PC300_DEBUG_INTR
+ printk("cpc_intr: spurious intr %d\n", irq);
+#endif
+ return IRQ_NONE; /* spurious intr */
+ }
+
+ if (card->hw.rambase == 0) {
+#ifdef PC300_DEBUG_INTR
+ printk("cpc_intr: spurious intr2 %d\n", irq);
+#endif
+ return IRQ_NONE; /* spurious intr */
+ }
+
+ switch (card->hw.type) {
+ case PC300_RSV:
+ case PC300_X21:
+ sca_intr(card);
+ break;
+
+ case PC300_TE:
+ while ( (plx_status = (cpc_readb(card->hw.plxbase + card->hw.intctl_reg) &
+ (PLX_9050_LINT1_STATUS | PLX_9050_LINT2_STATUS))) != 0) {
+ if (plx_status & PLX_9050_LINT1_STATUS) { /* SCA Interrupt */
+ sca_intr(card);
+ }
+ if (plx_status & PLX_9050_LINT2_STATUS) { /* FALC Interrupt */
+ falc_intr(card);
+ }
+ }
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+void cpc_sca_status(pc300_t * card, int ch)
+{
+ ucchar ilar;
+ void __iomem *scabase = card->hw.scabase;
+ unsigned long flags;
+
+ tx_dma_buf_check(card, ch);
+ rx_dma_buf_check(card, ch);
+ ilar = cpc_readb(scabase + ILAR);
+ printk ("ILAR=0x%02x, WCRL=0x%02x, PCR=0x%02x, BTCR=0x%02x, BOLR=0x%02x\n",
+ ilar, cpc_readb(scabase + WCRL), cpc_readb(scabase + PCR),
+ cpc_readb(scabase + BTCR), cpc_readb(scabase + BOLR));
+ printk("TX_CDA=0x%08x, TX_EDA=0x%08x\n",
+ cpc_readl(scabase + DTX_REG(CDAL, ch)),
+ cpc_readl(scabase + DTX_REG(EDAL, ch)));
+ printk("RX_CDA=0x%08x, RX_EDA=0x%08x, BFL=0x%04x\n",
+ cpc_readl(scabase + DRX_REG(CDAL, ch)),
+ cpc_readl(scabase + DRX_REG(EDAL, ch)),
+ cpc_readw(scabase + DRX_REG(BFLL, ch)));
+ printk("DMER=0x%02x, DSR_TX=0x%02x, DSR_RX=0x%02x\n",
+ cpc_readb(scabase + DMER), cpc_readb(scabase + DSR_TX(ch)),
+ cpc_readb(scabase + DSR_RX(ch)));
+ printk("DMR_TX=0x%02x, DMR_RX=0x%02x, DIR_TX=0x%02x, DIR_RX=0x%02x\n",
+ cpc_readb(scabase + DMR_TX(ch)), cpc_readb(scabase + DMR_RX(ch)),
+ cpc_readb(scabase + DIR_TX(ch)),
+ cpc_readb(scabase + DIR_RX(ch)));
+ printk("DCR_TX=0x%02x, DCR_RX=0x%02x, FCT_TX=0x%02x, FCT_RX=0x%02x\n",
+ cpc_readb(scabase + DCR_TX(ch)), cpc_readb(scabase + DCR_RX(ch)),
+ cpc_readb(scabase + FCT_TX(ch)),
+ cpc_readb(scabase + FCT_RX(ch)));
+ printk("MD0=0x%02x, MD1=0x%02x, MD2=0x%02x, MD3=0x%02x, IDL=0x%02x\n",
+ cpc_readb(scabase + M_REG(MD0, ch)),
+ cpc_readb(scabase + M_REG(MD1, ch)),
+ cpc_readb(scabase + M_REG(MD2, ch)),
+ cpc_readb(scabase + M_REG(MD3, ch)),
+ cpc_readb(scabase + M_REG(IDL, ch)));
+ printk("CMD=0x%02x, SA0=0x%02x, SA1=0x%02x, TFN=0x%02x, CTL=0x%02x\n",
+ cpc_readb(scabase + M_REG(CMD, ch)),
+ cpc_readb(scabase + M_REG(SA0, ch)),
+ cpc_readb(scabase + M_REG(SA1, ch)),
+ cpc_readb(scabase + M_REG(TFN, ch)),
+ cpc_readb(scabase + M_REG(CTL, ch)));
+ printk("ST0=0x%02x, ST1=0x%02x, ST2=0x%02x, ST3=0x%02x, ST4=0x%02x\n",
+ cpc_readb(scabase + M_REG(ST0, ch)),
+ cpc_readb(scabase + M_REG(ST1, ch)),
+ cpc_readb(scabase + M_REG(ST2, ch)),
+ cpc_readb(scabase + M_REG(ST3, ch)),
+ cpc_readb(scabase + M_REG(ST4, ch)));
+ printk ("CST0=0x%02x, CST1=0x%02x, CST2=0x%02x, CST3=0x%02x, FST=0x%02x\n",
+ cpc_readb(scabase + M_REG(CST0, ch)),
+ cpc_readb(scabase + M_REG(CST1, ch)),
+ cpc_readb(scabase + M_REG(CST2, ch)),
+ cpc_readb(scabase + M_REG(CST3, ch)),
+ cpc_readb(scabase + M_REG(FST, ch)));
+ printk("TRC0=0x%02x, TRC1=0x%02x, RRC=0x%02x, TBN=0x%02x, RBN=0x%02x\n",
+ cpc_readb(scabase + M_REG(TRC0, ch)),
+ cpc_readb(scabase + M_REG(TRC1, ch)),
+ cpc_readb(scabase + M_REG(RRC, ch)),
+ cpc_readb(scabase + M_REG(TBN, ch)),
+ cpc_readb(scabase + M_REG(RBN, ch)));
+ printk("TFS=0x%02x, TNR0=0x%02x, TNR1=0x%02x, RNR=0x%02x\n",
+ cpc_readb(scabase + M_REG(TFS, ch)),
+ cpc_readb(scabase + M_REG(TNR0, ch)),
+ cpc_readb(scabase + M_REG(TNR1, ch)),
+ cpc_readb(scabase + M_REG(RNR, ch)));
+ printk("TCR=0x%02x, RCR=0x%02x, TNR1=0x%02x, RNR=0x%02x\n",
+ cpc_readb(scabase + M_REG(TCR, ch)),
+ cpc_readb(scabase + M_REG(RCR, ch)),
+ cpc_readb(scabase + M_REG(TNR1, ch)),
+ cpc_readb(scabase + M_REG(RNR, ch)));
+ printk("TXS=0x%02x, RXS=0x%02x, EXS=0x%02x, TMCT=0x%02x, TMCR=0x%02x\n",
+ cpc_readb(scabase + M_REG(TXS, ch)),
+ cpc_readb(scabase + M_REG(RXS, ch)),
+ cpc_readb(scabase + M_REG(EXS, ch)),
+ cpc_readb(scabase + M_REG(TMCT, ch)),
+ cpc_readb(scabase + M_REG(TMCR, ch)));
+ printk("IE0=0x%02x, IE1=0x%02x, IE2=0x%02x, IE4=0x%02x, FIE=0x%02x\n",
+ cpc_readb(scabase + M_REG(IE0, ch)),
+ cpc_readb(scabase + M_REG(IE1, ch)),
+ cpc_readb(scabase + M_REG(IE2, ch)),
+ cpc_readb(scabase + M_REG(IE4, ch)),
+ cpc_readb(scabase + M_REG(FIE, ch)));
+ printk("IER0=0x%08x\n", cpc_readl(scabase + IER0));
+
+ if (ilar != 0) {
+ CPC_LOCK(card, flags);
+ cpc_writeb(scabase + ILAR, ilar);
+ cpc_writeb(scabase + DMER, 0x80);
+ CPC_UNLOCK(card, flags);
+ }
+}
+
+void cpc_falc_status(pc300_t * card, int ch)
+{
+ pc300ch_t *chan = &card->chan[ch];
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ unsigned long flags;
+
+ CPC_LOCK(card, flags);
+ printk("CH%d: %s %s %d channels\n",
+ ch, (pfalc->sync ? "SYNC" : ""), (pfalc->active ? "ACTIVE" : ""),
+ pfalc->num_channels);
+
+ printk(" pden=%d, los=%d, losr=%d, lfa=%d, farec=%d\n",
+ pfalc->pden, pfalc->los, pfalc->losr, pfalc->lfa, pfalc->farec);
+ printk(" lmfa=%d, ais=%d, sec=%d, es=%d, rai=%d\n",
+ pfalc->lmfa, pfalc->ais, pfalc->sec, pfalc->es, pfalc->rai);
+ printk(" bec=%d, fec=%d, cvc=%d, cec=%d, ebc=%d\n",
+ pfalc->bec, pfalc->fec, pfalc->cvc, pfalc->cec, pfalc->ebc);
+
+ printk("\n");
+ printk(" STATUS: %s %s %s %s %s %s\n",
+ (pfalc->red_alarm ? "RED" : ""),
+ (pfalc->blue_alarm ? "BLU" : ""),
+ (pfalc->yellow_alarm ? "YEL" : ""),
+ (pfalc->loss_fa ? "LFA" : ""),
+ (pfalc->loss_mfa ? "LMF" : ""), (pfalc->prbs ? "PRB" : ""));
+ CPC_UNLOCK(card, flags);
+}
+
+int cpc_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if ((new_mtu < 128) || (new_mtu > PC300_DEF_MTU))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+int cpc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ pc300dev_t *d = (pc300dev_t *) dev->priv;
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ pc300conf_t conf_aux;
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ int ch = chan->channel;
+ void __user *arg = ifr->ifr_data;
+ struct if_settings *settings = &ifr->ifr_settings;
+ void __iomem *scabase = card->hw.scabase;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case SIOCGPC300CONF:
+#ifdef CONFIG_PC300_MLPPP
+ if (conf->proto != PC300_PROTO_MLPPP) {
+ conf->proto = hdlc->proto.id;
+ }
+#else
+ conf->proto = hdlc->proto.id;
+#endif
+ memcpy(&conf_aux.conf, conf, sizeof(pc300chconf_t));
+ memcpy(&conf_aux.hw, &card->hw, sizeof(pc300hw_t));
+ if (!arg ||
+ copy_to_user(arg, &conf_aux, sizeof(pc300conf_t)))
+ return -EINVAL;
+ return 0;
+ case SIOCSPC300CONF:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (!arg ||
+ copy_from_user(&conf_aux.conf, arg, sizeof(pc300chconf_t)))
+ return -EINVAL;
+ if (card->hw.cpld_id < 0x02 &&
+ conf_aux.conf.fr_mode == PC300_FR_UNFRAMED) {
+ /* CPLD_ID < 0x02 doesn't support Unframed E1 */
+ return -EINVAL;
+ }
+#ifdef CONFIG_PC300_MLPPP
+ if (conf_aux.conf.proto == PC300_PROTO_MLPPP) {
+ if (conf->proto != PC300_PROTO_MLPPP) {
+ memcpy(conf, &conf_aux.conf, sizeof(pc300chconf_t));
+ cpc_tty_init(d); /* init TTY driver */
+ }
+ } else {
+ if (conf_aux.conf.proto == 0xffff) {
+ if (conf->proto == PC300_PROTO_MLPPP){
+ /* ifdown interface */
+ cpc_close(dev);
+ }
+ } else {
+ memcpy(conf, &conf_aux.conf, sizeof(pc300chconf_t));
+ hdlc->proto.id = conf->proto;
+ }
+ }
+#else
+ memcpy(conf, &conf_aux.conf, sizeof(pc300chconf_t));
+ hdlc->proto.id = conf->proto;
+#endif
+ return 0;
+ case SIOCGPC300STATUS:
+ cpc_sca_status(card, ch);
+ return 0;
+ case SIOCGPC300FALCSTATUS:
+ cpc_falc_status(card, ch);
+ return 0;
+
+ case SIOCGPC300UTILSTATS:
+ {
+ if (!arg) { /* clear statistics */
+ memset(&hdlc->stats, 0, sizeof(struct net_device_stats));
+ if (card->hw.type == PC300_TE) {
+ memset(&chan->falc, 0, sizeof(falc_t));
+ }
+ } else {
+ pc300stats_t pc300stats;
+
+ memset(&pc300stats, 0, sizeof(pc300stats_t));
+ pc300stats.hw_type = card->hw.type;
+ pc300stats.line_on = card->chan[ch].d.line_on;
+ pc300stats.line_off = card->chan[ch].d.line_off;
+ memcpy(&pc300stats.gen_stats, &hdlc->stats,
+ sizeof(struct net_device_stats));
+ if (card->hw.type == PC300_TE)
+ memcpy(&pc300stats.te_stats,&chan->falc,sizeof(falc_t));
+ if (copy_to_user(arg, &pc300stats, sizeof(pc300stats_t)))
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case SIOCGPC300UTILSTATUS:
+ {
+ struct pc300status pc300status;
+
+ pc300status.hw_type = card->hw.type;
+ if (card->hw.type == PC300_TE) {
+ pc300status.te_status.sync = chan->falc.sync;
+ pc300status.te_status.red_alarm = chan->falc.red_alarm;
+ pc300status.te_status.blue_alarm = chan->falc.blue_alarm;
+ pc300status.te_status.loss_fa = chan->falc.loss_fa;
+ pc300status.te_status.yellow_alarm =chan->falc.yellow_alarm;
+ pc300status.te_status.loss_mfa = chan->falc.loss_mfa;
+ pc300status.te_status.prbs = chan->falc.prbs;
+ } else {
+ pc300status.gen_status.dcd =
+ !(cpc_readb (scabase + M_REG(ST3, ch)) & ST3_DCD);
+ pc300status.gen_status.cts =
+ !(cpc_readb (scabase + M_REG(ST3, ch)) & ST3_CTS);
+ pc300status.gen_status.rts =
+ !(cpc_readb (scabase + M_REG(CTL, ch)) & CTL_RTS);
+ pc300status.gen_status.dtr =
+ !(cpc_readb (scabase + M_REG(CTL, ch)) & CTL_DTR);
+ /* There is no DSR in HD64572 */
+ }
+ if (!arg
+ || copy_to_user(arg, &pc300status, sizeof(pc300status_t)))
+ return -EINVAL;
+ return 0;
+ }
+
+ case SIOCSPC300TRACE:
+ /* Sets/resets a trace_flag for the respective device */
+ if (!arg || copy_from_user(&d->trace_on, arg,sizeof(unsigned char)))
+ return -EINVAL;
+ return 0;
+
+ case SIOCSPC300LOOPBACK:
+ {
+ struct pc300loopback pc300loop;
+
+ /* TE boards only */
+ if (card->hw.type != PC300_TE)
+ return -EINVAL;
+
+ if (!arg ||
+ copy_from_user(&pc300loop, arg, sizeof(pc300loopback_t)))
+ return -EINVAL;
+ switch (pc300loop.loop_type) {
+ case PC300LOCLOOP: /* Turn the local loop on/off */
+ falc_local_loop(card, ch, pc300loop.loop_on);
+ return 0;
+
+ case PC300REMLOOP: /* Turn the remote loop on/off */
+ falc_remote_loop(card, ch, pc300loop.loop_on);
+ return 0;
+
+ case PC300PAYLOADLOOP: /* Turn the payload loop on/off */
+ falc_payload_loop(card, ch, pc300loop.loop_on);
+ return 0;
+
+ case PC300GENLOOPUP: /* Generate loop UP */
+ if (pc300loop.loop_on) {
+ falc_generate_loop_up_code (card, ch);
+ } else {
+ turn_off_xlu(card, ch);
+ }
+ return 0;
+
+ case PC300GENLOOPDOWN: /* Generate loop DOWN */
+ if (pc300loop.loop_on) {
+ falc_generate_loop_down_code (card, ch);
+ } else {
+ turn_off_xld(card, ch);
+ }
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ case SIOCSPC300PATTERNTEST:
+ /* Turn the pattern test on/off and show the errors counter */
+ {
+ struct pc300patterntst pc300patrntst;
+
+ /* TE boards only */
+ if (card->hw.type != PC300_TE)
+ return -EINVAL;
+
+ if (card->hw.cpld_id < 0x02) {
+ /* CPLD_ID < 0x02 doesn't support pattern test */
+ return -EINVAL;
+ }
+
+ if (!arg ||
+ copy_from_user(&pc300patrntst,arg,sizeof(pc300patterntst_t)))
+ return -EINVAL;
+ if (pc300patrntst.patrntst_on == 2) {
+ if (chan->falc.prbs == 0) {
+ falc_pattern_test(card, ch, 1);
+ }
+ pc300patrntst.num_errors =
+ falc_pattern_test_error(card, ch);
+ if (!arg
+ || copy_to_user(arg, &pc300patrntst,
+ sizeof (pc300patterntst_t)))
+ return -EINVAL;
+ } else {
+ falc_pattern_test(card, ch, pc300patrntst.patrntst_on);
+ }
+ return 0;
+ }
+
+ case SIOCWANDEV:
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ {
+ const size_t size = sizeof(sync_serial_settings);
+ ifr->ifr_settings.type = conf->media;
+ if (ifr->ifr_settings.size < size) {
+ /* data size wanted */
+ ifr->ifr_settings.size = size;
+ return -ENOBUFS;
+ }
+
+ if (copy_to_user(settings->ifs_ifsu.sync,
+ &conf->phys_settings, size)) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+
+ case IF_IFACE_V35:
+ case IF_IFACE_V24:
+ case IF_IFACE_X21:
+ {
+ const size_t size = sizeof(sync_serial_settings);
+
+ if (!capable(CAP_NET_ADMIN)) {
+ return -EPERM;
+ }
+ /* incorrect data len? */
+ if (ifr->ifr_settings.size != size) {
+ return -ENOBUFS;
+ }
+
+ if (copy_from_user(&conf->phys_settings,
+ settings->ifs_ifsu.sync, size)) {
+ return -EFAULT;
+ }
+
+ if (conf->phys_settings.loopback) {
+ cpc_writeb(card->hw.scabase + M_REG(MD2, ch),
+ cpc_readb(card->hw.scabase + M_REG(MD2, ch)) |
+ MD2_LOOP_MIR);
+ }
+ conf->media = ifr->ifr_settings.type;
+ return 0;
+ }
+
+ case IF_IFACE_T1:
+ case IF_IFACE_E1:
+ {
+ const size_t te_size = sizeof(te1_settings);
+ const size_t size = sizeof(sync_serial_settings);
+
+ if (!capable(CAP_NET_ADMIN)) {
+ return -EPERM;
+ }
+
+ /* incorrect data len? */
+ if (ifr->ifr_settings.size != te_size) {
+ return -ENOBUFS;
+ }
+
+ if (copy_from_user(&conf->phys_settings,
+ settings->ifs_ifsu.te1, size)) {
+ return -EFAULT;
+ }/* Ignoring HDLC slot_map for a while */
+
+ if (conf->phys_settings.loopback) {
+ cpc_writeb(card->hw.scabase + M_REG(MD2, ch),
+ cpc_readb(card->hw.scabase + M_REG(MD2, ch)) |
+ MD2_LOOP_MIR);
+ }
+ conf->media = ifr->ifr_settings.type;
+ return 0;
+ }
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+static struct net_device_stats *cpc_get_stats(struct net_device *dev)
+{
+ return hdlc_stats(dev);
+}
+
+static int clock_rate_calc(uclong rate, uclong clock, int *br_io)
+{
+ int br, tc;
+ int br_pwr, error;
+
+ if (rate == 0)
+ return (0);
+
+ for (br = 0, br_pwr = 1; br <= 9; br++, br_pwr <<= 1) {
+ if ((tc = clock / br_pwr / rate) <= 0xff) {
+ *br_io = br;
+ break;
+ }
+ }
+
+ if (tc <= 0xff) {
+ error = ((rate - (clock / br_pwr / rate)) / rate) * 1000;
+ /* Errors bigger than +/- 1% won't be tolerated */
+ if (error < -10 || error > 10)
+ return (-1);
+ else
+ return (tc);
+ } else {
+ return (-1);
+ }
+}
+
+int ch_config(pc300dev_t * d)
+{
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300chconf_t *conf = (pc300chconf_t *) & chan->conf;
+ pc300_t *card = (pc300_t *) chan->card;
+ void __iomem *scabase = card->hw.scabase;
+ void __iomem *plxbase = card->hw.plxbase;
+ int ch = chan->channel;
+ uclong clkrate = chan->conf.phys_settings.clock_rate;
+ uclong clktype = chan->conf.phys_settings.clock_type;
+ ucshort encoding = chan->conf.proto_settings.encoding;
+ ucshort parity = chan->conf.proto_settings.parity;
+ int tmc, br;
+ ucchar md0, md2;
+
+ /* Reset the channel */
+ cpc_writeb(scabase + M_REG(CMD, ch), CMD_CH_RST);
+
+ /* Configure the SCA registers */
+ switch (parity) {
+ case PARITY_NONE:
+ md0 = MD0_BIT_SYNC;
+ break;
+ case PARITY_CRC16_PR0:
+ md0 = MD0_CRC16_0|MD0_CRCC0|MD0_BIT_SYNC;
+ break;
+ case PARITY_CRC16_PR1:
+ md0 = MD0_CRC16_1|MD0_CRCC0|MD0_BIT_SYNC;
+ break;
+ case PARITY_CRC32_PR1_CCITT:
+ md0 = MD0_CRC32|MD0_CRCC0|MD0_BIT_SYNC;
+ break;
+ case PARITY_CRC16_PR1_CCITT:
+ default:
+ md0 = MD0_CRC_CCITT|MD0_CRCC0|MD0_BIT_SYNC;
+ break;
+ }
+ switch (encoding) {
+ case ENCODING_NRZI:
+ md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_NRZI;
+ break;
+ case ENCODING_FM_MARK: /* FM1 */
+ md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_FM|MD2_FM1;
+ break;
+ case ENCODING_FM_SPACE: /* FM0 */
+ md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_FM|MD2_FM0;
+ break;
+ case ENCODING_MANCHESTER: /* It's not working... */
+ md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_FM|MD2_MANCH;
+ break;
+ case ENCODING_NRZ:
+ default:
+ md2 = MD2_F_DUPLEX|MD2_ADPLL_X8|MD2_NRZ;
+ break;
+ }
+ cpc_writeb(scabase + M_REG(MD0, ch), md0);
+ cpc_writeb(scabase + M_REG(MD1, ch), 0);
+ cpc_writeb(scabase + M_REG(MD2, ch), md2);
+ cpc_writeb(scabase + M_REG(IDL, ch), 0x7e);
+ cpc_writeb(scabase + M_REG(CTL, ch), CTL_URSKP | CTL_IDLC);
+
+ /* Configure HW media */
+ switch (card->hw.type) {
+ case PC300_RSV:
+ if (conf->media == IF_IFACE_V35) {
+ cpc_writel((plxbase + card->hw.gpioc_reg),
+ cpc_readl(plxbase + card->hw.gpioc_reg) | PC300_CHMEDIA_MASK(ch));
+ } else {
+ cpc_writel((plxbase + card->hw.gpioc_reg),
+ cpc_readl(plxbase + card->hw.gpioc_reg) & ~PC300_CHMEDIA_MASK(ch));
+ }
+ break;
+
+ case PC300_X21:
+ break;
+
+ case PC300_TE:
+ te_config(card, ch);
+ break;
+ }
+
+ switch (card->hw.type) {
+ case PC300_RSV:
+ case PC300_X21:
+ if (clktype == CLOCK_INT || clktype == CLOCK_TXINT) {
+ /* Calculate the clkrate parameters */
+ tmc = clock_rate_calc(clkrate, card->hw.clock, &br);
+ cpc_writeb(scabase + M_REG(TMCT, ch), tmc);
+ cpc_writeb(scabase + M_REG(TXS, ch),
+ (TXS_DTRXC | TXS_IBRG | br));
+ if (clktype == CLOCK_INT) {
+ cpc_writeb(scabase + M_REG(TMCR, ch), tmc);
+ cpc_writeb(scabase + M_REG(RXS, ch),
+ (RXS_IBRG | br));
+ } else {
+ cpc_writeb(scabase + M_REG(TMCR, ch), 1);
+ cpc_writeb(scabase + M_REG(RXS, ch), 0);
+ }
+ if (card->hw.type == PC300_X21) {
+ cpc_writeb(scabase + M_REG(GPO, ch), 1);
+ cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1 | EXS_RES1);
+ } else {
+ cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1);
+ }
+ } else {
+ cpc_writeb(scabase + M_REG(TMCT, ch), 1);
+ if (clktype == CLOCK_EXT) {
+ cpc_writeb(scabase + M_REG(TXS, ch),
+ TXS_DTRXC);
+ } else {
+ cpc_writeb(scabase + M_REG(TXS, ch),
+ TXS_DTRXC|TXS_RCLK);
+ }
+ cpc_writeb(scabase + M_REG(TMCR, ch), 1);
+ cpc_writeb(scabase + M_REG(RXS, ch), 0);
+ if (card->hw.type == PC300_X21) {
+ cpc_writeb(scabase + M_REG(GPO, ch), 0);
+ cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1 | EXS_RES1);
+ } else {
+ cpc_writeb(scabase + M_REG(EXS, ch), EXS_TES1);
+ }
+ }
+ break;
+
+ case PC300_TE:
+ /* SCA always receives clock from the FALC chip */
+ cpc_writeb(scabase + M_REG(TMCT, ch), 1);
+ cpc_writeb(scabase + M_REG(TXS, ch), 0);
+ cpc_writeb(scabase + M_REG(TMCR, ch), 1);
+ cpc_writeb(scabase + M_REG(RXS, ch), 0);
+ cpc_writeb(scabase + M_REG(EXS, ch), 0);
+ break;
+ }
+
+ /* Enable Interrupts */
+ cpc_writel(scabase + IER0,
+ cpc_readl(scabase + IER0) |
+ IR0_M(IR0_RXINTA, ch) |
+ IR0_DRX(IR0_EFT | IR0_DMIA | IR0_DMIB, ch) |
+ IR0_DTX(IR0_EFT | IR0_DMIA | IR0_DMIB, ch));
+ cpc_writeb(scabase + M_REG(IE0, ch),
+ cpc_readl(scabase + M_REG(IE0, ch)) | IE0_RXINTA);
+ cpc_writeb(scabase + M_REG(IE1, ch),
+ cpc_readl(scabase + M_REG(IE1, ch)) | IE1_CDCD);
+
+ return 0;
+}
+
+int rx_config(pc300dev_t * d)
+{
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ void __iomem *scabase = card->hw.scabase;
+ int ch = chan->channel;
+
+ cpc_writeb(scabase + DSR_RX(ch), 0);
+
+ /* General RX settings */
+ cpc_writeb(scabase + M_REG(RRC, ch), 0);
+ cpc_writeb(scabase + M_REG(RNR, ch), 16);
+
+ /* Enable reception */
+ cpc_writeb(scabase + M_REG(CMD, ch), CMD_RX_CRC_INIT);
+ cpc_writeb(scabase + M_REG(CMD, ch), CMD_RX_ENA);
+
+ /* Initialize DMA stuff */
+ chan->rx_first_bd = 0;
+ chan->rx_last_bd = N_DMA_RX_BUF - 1;
+ rx_dma_buf_init(card, ch);
+ cpc_writeb(scabase + DCR_RX(ch), DCR_FCT_CLR);
+ cpc_writeb(scabase + DMR_RX(ch), (DMR_TMOD | DMR_NF));
+ cpc_writeb(scabase + DIR_RX(ch), (DIR_EOM | DIR_BOF));
+
+ /* Start DMA */
+ rx_dma_start(card, ch);
+
+ return 0;
+}
+
+int tx_config(pc300dev_t * d)
+{
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ void __iomem *scabase = card->hw.scabase;
+ int ch = chan->channel;
+
+ cpc_writeb(scabase + DSR_TX(ch), 0);
+
+ /* General TX settings */
+ cpc_writeb(scabase + M_REG(TRC0, ch), 0);
+ cpc_writeb(scabase + M_REG(TFS, ch), 32);
+ cpc_writeb(scabase + M_REG(TNR0, ch), 20);
+ cpc_writeb(scabase + M_REG(TNR1, ch), 48);
+ cpc_writeb(scabase + M_REG(TCR, ch), 8);
+
+ /* Enable transmission */
+ cpc_writeb(scabase + M_REG(CMD, ch), CMD_TX_CRC_INIT);
+
+ /* Initialize DMA stuff */
+ chan->tx_first_bd = 0;
+ chan->tx_next_bd = 0;
+ tx_dma_buf_init(card, ch);
+ cpc_writeb(scabase + DCR_TX(ch), DCR_FCT_CLR);
+ cpc_writeb(scabase + DMR_TX(ch), (DMR_TMOD | DMR_NF));
+ cpc_writeb(scabase + DIR_TX(ch), (DIR_EOM | DIR_BOF | DIR_UDRF));
+ cpc_writel(scabase + DTX_REG(CDAL, ch), TX_BD_ADDR(ch, chan->tx_first_bd));
+ cpc_writel(scabase + DTX_REG(EDAL, ch), TX_BD_ADDR(ch, chan->tx_next_bd));
+
+ return 0;
+}
+
+static int cpc_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ pc300dev_t *d = (pc300dev_t *)dev->priv;
+ pc300ch_t *chan = (pc300ch_t *)d->chan;
+ pc300_t *card = (pc300_t *)chan->card;
+ pc300chconf_t *conf = (pc300chconf_t *)&chan->conf;
+
+ if (card->hw.type == PC300_TE) {
+ if (encoding != ENCODING_NRZ && encoding != ENCODING_NRZI) {
+ return -EINVAL;
+ }
+ } else {
+ if (encoding != ENCODING_NRZ && encoding != ENCODING_NRZI &&
+ encoding != ENCODING_FM_MARK && encoding != ENCODING_FM_SPACE) {
+ /* Driver doesn't support ENCODING_MANCHESTER yet */
+ return -EINVAL;
+ }
+ }
+
+ if (parity != PARITY_NONE && parity != PARITY_CRC16_PR0 &&
+ parity != PARITY_CRC16_PR1 && parity != PARITY_CRC32_PR1_CCITT &&
+ parity != PARITY_CRC16_PR1_CCITT) {
+ return -EINVAL;
+ }
+
+ conf->proto_settings.encoding = encoding;
+ conf->proto_settings.parity = parity;
+ return 0;
+}
+
+void cpc_opench(pc300dev_t * d)
+{
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ int ch = chan->channel;
+ void __iomem *scabase = card->hw.scabase;
+
+ ch_config(d);
+
+ rx_config(d);
+
+ tx_config(d);
+
+ /* Assert RTS and DTR */
+ cpc_writeb(scabase + M_REG(CTL, ch),
+ cpc_readb(scabase + M_REG(CTL, ch)) & ~(CTL_RTS | CTL_DTR));
+}
+
+void cpc_closech(pc300dev_t * d)
+{
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ falc_t *pfalc = (falc_t *) & chan->falc;
+ int ch = chan->channel;
+
+ cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_CH_RST);
+ rx_dma_stop(card, ch);
+ tx_dma_stop(card, ch);
+
+ if (card->hw.type == PC300_TE) {
+ memset(pfalc, 0, sizeof(falc_t));
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) &
+ ~((CPLD_REG2_FALC_TX_CLK | CPLD_REG2_FALC_RX_CLK |
+ CPLD_REG2_FALC_LED2) << (2 * ch)));
+ /* Reset the FALC chip */
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+ (CPLD_REG1_FALC_RESET << (2 * ch)));
+ udelay(10000);
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) &
+ ~(CPLD_REG1_FALC_RESET << (2 * ch)));
+ }
+}
+
+int cpc_open(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ pc300dev_t *d = (pc300dev_t *) dev->priv;
+ struct ifreq ifr;
+ int result;
+
+#ifdef PC300_DEBUG_OTHER
+ printk("pc300: cpc_open");
+#endif
+
+ if (hdlc->proto.id == IF_PROTO_PPP) {
+ d->if_ptr = &hdlc->state.ppp.pppdev;
+ }
+
+ result = hdlc_open(dev);
+ if (hdlc->proto.id == IF_PROTO_PPP) {
+ dev->priv = d;
+ }
+ if (result) {
+ return result;
+ }
+
+ sprintf(ifr.ifr_name, "%s", dev->name);
+ cpc_opench(d);
+ netif_start_queue(dev);
+ return 0;
+}
+
+int cpc_close(struct net_device *dev)
+{
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ pc300dev_t *d = (pc300dev_t *) dev->priv;
+ pc300ch_t *chan = (pc300ch_t *) d->chan;
+ pc300_t *card = (pc300_t *) chan->card;
+ unsigned long flags;
+
+#ifdef PC300_DEBUG_OTHER
+ printk("pc300: cpc_close");
+#endif
+
+ netif_stop_queue(dev);
+
+ CPC_LOCK(card, flags);
+ cpc_closech(d);
+ CPC_UNLOCK(card, flags);
+
+ hdlc_close(dev);
+ if (hdlc->proto.id == IF_PROTO_PPP) {
+ d->if_ptr = NULL;
+ }
+#ifdef CONFIG_PC300_MLPPP
+ if (chan->conf.proto == PC300_PROTO_MLPPP) {
+ cpc_tty_unregister_service(d);
+ chan->conf.proto = 0xffff;
+ }
+#endif
+
+ return 0;
+}
+
+static uclong detect_ram(pc300_t * card)
+{
+ uclong i;
+ ucchar data;
+ void __iomem *rambase = card->hw.rambase;
+
+ card->hw.ramsize = PC300_RAMSIZE;
+ /* Let's find out how much RAM is present on this board */
+ for (i = 0; i < card->hw.ramsize; i++) {
+ data = (ucchar) (i & 0xff);
+ cpc_writeb(rambase + i, data);
+ if (cpc_readb(rambase + i) != data) {
+ break;
+ }
+ }
+ return (i);
+}
+
+static void plx_init(pc300_t * card)
+{
+ struct RUNTIME_9050 __iomem *plx_ctl = card->hw.plxbase;
+
+ /* Reset PLX */
+ cpc_writel(&plx_ctl->init_ctrl,
+ cpc_readl(&plx_ctl->init_ctrl) | 0x40000000);
+ udelay(10000L);
+ cpc_writel(&plx_ctl->init_ctrl,
+ cpc_readl(&plx_ctl->init_ctrl) & ~0x40000000);
+
+ /* Reload Config. Registers from EEPROM */
+ cpc_writel(&plx_ctl->init_ctrl,
+ cpc_readl(&plx_ctl->init_ctrl) | 0x20000000);
+ udelay(10000L);
+ cpc_writel(&plx_ctl->init_ctrl,
+ cpc_readl(&plx_ctl->init_ctrl) & ~0x20000000);
+
+}
+
+static inline void show_version(void)
+{
+ char *rcsvers, *rcsdate, *tmp;
+
+ rcsvers = strchr(rcsid, ' ');
+ rcsvers++;
+ tmp = strchr(rcsvers, ' ');
+ *tmp++ = '\0';
+ rcsdate = strchr(tmp, ' ');
+ rcsdate++;
+ tmp = strrchr(rcsdate, ' ');
+ *tmp = '\0';
+ printk(KERN_INFO "Cyclades-PC300 driver %s %s (built %s %s)\n",
+ rcsvers, rcsdate, __DATE__, __TIME__);
+} /* show_version */
+
+static void cpc_init_card(pc300_t * card)
+{
+ int i, devcount = 0;
+ static int board_nbr = 1;
+
+ /* Enable interrupts on the PCI bridge */
+ plx_init(card);
+ cpc_writew(card->hw.plxbase + card->hw.intctl_reg,
+ cpc_readw(card->hw.plxbase + card->hw.intctl_reg) | 0x0040);
+
+#ifdef USE_PCI_CLOCK
+ /* Set board clock to PCI clock */
+ cpc_writel(card->hw.plxbase + card->hw.gpioc_reg,
+ cpc_readl(card->hw.plxbase + card->hw.gpioc_reg) | 0x00000004UL);
+ card->hw.clock = PC300_PCI_CLOCK;
+#else
+ /* Set board clock to internal oscillator clock */
+ cpc_writel(card->hw.plxbase + card->hw.gpioc_reg,
+ cpc_readl(card->hw.plxbase + card->hw.gpioc_reg) & ~0x00000004UL);
+ card->hw.clock = PC300_OSC_CLOCK;
+#endif
+
+ /* Detect actual on-board RAM size */
+ card->hw.ramsize = detect_ram(card);
+
+ /* Set Global SCA-II registers */
+ cpc_writeb(card->hw.scabase + PCR, PCR_PR2);
+ cpc_writeb(card->hw.scabase + BTCR, 0x10);
+ cpc_writeb(card->hw.scabase + WCRL, 0);
+ cpc_writeb(card->hw.scabase + DMER, 0x80);
+
+ if (card->hw.type == PC300_TE) {
+ ucchar reg1;
+
+ /* Check CPLD version */
+ reg1 = cpc_readb(card->hw.falcbase + CPLD_REG1);
+ cpc_writeb(card->hw.falcbase + CPLD_REG1, (reg1 + 0x5a));
+ if (cpc_readb(card->hw.falcbase + CPLD_REG1) == reg1) {
+ /* New CPLD */
+ card->hw.cpld_id = cpc_readb(card->hw.falcbase + CPLD_ID_REG);
+ card->hw.cpld_reg1 = CPLD_V2_REG1;
+ card->hw.cpld_reg2 = CPLD_V2_REG2;
+ } else {
+ /* old CPLD */
+ card->hw.cpld_id = 0;
+ card->hw.cpld_reg1 = CPLD_REG1;
+ card->hw.cpld_reg2 = CPLD_REG2;
+ cpc_writeb(card->hw.falcbase + CPLD_REG1, reg1);
+ }
+
+ /* Enable the board's global clock */
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg1,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg1) |
+ CPLD_REG1_GLOBAL_CLK);
+
+ }
+
+ for (i = 0; i < card->hw.nchan; i++) {
+ pc300ch_t *chan = &card->chan[i];
+ pc300dev_t *d = &chan->d;
+ hdlc_device *hdlc;
+ struct net_device *dev;
+
+ chan->card = card;
+ chan->channel = i;
+ chan->conf.phys_settings.clock_rate = 0;
+ chan->conf.phys_settings.clock_type = CLOCK_EXT;
+ chan->conf.proto_settings.encoding = ENCODING_NRZ;
+ chan->conf.proto_settings.parity = PARITY_CRC16_PR1_CCITT;
+ switch (card->hw.type) {
+ case PC300_TE:
+ chan->conf.media = IF_IFACE_T1;
+ chan->conf.lcode = PC300_LC_B8ZS;
+ chan->conf.fr_mode = PC300_FR_ESF;
+ chan->conf.lbo = PC300_LBO_0_DB;
+ chan->conf.rx_sens = PC300_RX_SENS_SH;
+ chan->conf.tslot_bitmap = 0xffffffffUL;
+ break;
+
+ case PC300_X21:
+ chan->conf.media = IF_IFACE_X21;
+ break;
+
+ case PC300_RSV:
+ default:
+ chan->conf.media = IF_IFACE_V35;
+ break;
+ }
+ chan->conf.proto = IF_PROTO_PPP;
+ chan->tx_first_bd = 0;
+ chan->tx_next_bd = 0;
+ chan->rx_first_bd = 0;
+ chan->rx_last_bd = N_DMA_RX_BUF - 1;
+ chan->nfree_tx_bd = N_DMA_TX_BUF;
+
+ d->chan = chan;
+ d->tx_skb = NULL;
+ d->trace_on = 0;
+ d->line_on = 0;
+ d->line_off = 0;
+
+ dev = alloc_hdlcdev(NULL);
+ if (dev == NULL)
+ continue;
+
+ hdlc = dev_to_hdlc(dev);
+ hdlc->xmit = cpc_queue_xmit;
+ hdlc->attach = cpc_attach;
+ d->dev = dev;
+ dev->mem_start = card->hw.ramphys;
+ dev->mem_end = card->hw.ramphys + card->hw.ramsize - 1;
+ dev->irq = card->hw.irq;
+ dev->init = NULL;
+ dev->tx_queue_len = PC300_TX_QUEUE_LEN;
+ dev->mtu = PC300_DEF_MTU;
+
+ dev->open = cpc_open;
+ dev->stop = cpc_close;
+ dev->tx_timeout = cpc_tx_timeout;
+ dev->watchdog_timeo = PC300_TX_TIMEOUT;
+ dev->get_stats = cpc_get_stats;
+ dev->set_multicast_list = NULL;
+ dev->set_mac_address = NULL;
+ dev->change_mtu = cpc_change_mtu;
+ dev->do_ioctl = cpc_ioctl;
+
+ if (register_hdlc_device(dev) == 0) {
+ dev->priv = d; /* We need 'priv', hdlc doesn't */
+ printk("%s: Cyclades-PC300/", dev->name);
+ switch (card->hw.type) {
+ case PC300_TE:
+ if (card->hw.bus == PC300_PMC) {
+ printk("TE-M");
+ } else {
+ printk("TE ");
+ }
+ break;
+
+ case PC300_X21:
+ printk("X21 ");
+ break;
+
+ case PC300_RSV:
+ default:
+ printk("RSV ");
+ break;
+ }
+ printk (" #%d, %dKB of RAM at 0x%08x, IRQ%d, channel %d.\n",
+ board_nbr, card->hw.ramsize / 1024,
+ card->hw.ramphys, card->hw.irq, i + 1);
+ devcount++;
+ } else {
+ printk ("Dev%d on card(0x%08x): unable to allocate i/f name.\n",
+ i + 1, card->hw.ramphys);
+ free_netdev(dev);
+ continue;
+ }
+ }
+ spin_lock_init(&card->card_lock);
+
+ board_nbr++;
+}
+
+static int __devinit
+cpc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ static int first_time = 1;
+ ucchar cpc_rev_id;
+ int err = 0, eeprom_outdated = 0;
+ ucshort device_id;
+ pc300_t *card;
+
+ if (first_time) {
+ first_time = 0;
+ show_version();
+#ifdef CONFIG_PC300_MLPPP
+ cpc_tty_reset_var();
+#endif
+ }
+
+ card = (pc300_t *) kmalloc(sizeof(pc300_t), GFP_KERNEL);
+ if (card == NULL) {
+ printk("PC300 found at RAM 0x%08lx, "
+ "but could not allocate card structure.\n",
+ pci_resource_start(pdev, 3));
+ return -ENOMEM;
+ }
+ memset(card, 0, sizeof(pc300_t));
+
+ /* read PCI configuration area */
+ device_id = ent->device;
+ card->hw.irq = pdev->irq;
+ card->hw.iophys = pci_resource_start(pdev, 1);
+ card->hw.iosize = pci_resource_len(pdev, 1);
+ card->hw.scaphys = pci_resource_start(pdev, 2);
+ card->hw.scasize = pci_resource_len(pdev, 2);
+ card->hw.ramphys = pci_resource_start(pdev, 3);
+ card->hw.alloc_ramsize = pci_resource_len(pdev, 3);
+ card->hw.falcphys = pci_resource_start(pdev, 4);
+ card->hw.falcsize = pci_resource_len(pdev, 4);
+ card->hw.plxphys = pci_resource_start(pdev, 5);
+ card->hw.plxsize = pci_resource_len(pdev, 5);
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &cpc_rev_id);
+
+ switch (device_id) {
+ case PCI_DEVICE_ID_PC300_RX_1:
+ case PCI_DEVICE_ID_PC300_TE_1:
+ case PCI_DEVICE_ID_PC300_TE_M_1:
+ card->hw.nchan = 1;
+ break;
+
+ case PCI_DEVICE_ID_PC300_RX_2:
+ case PCI_DEVICE_ID_PC300_TE_2:
+ case PCI_DEVICE_ID_PC300_TE_M_2:
+ default:
+ card->hw.nchan = PC300_MAXCHAN;
+ break;
+ }
+#ifdef PC300_DEBUG_PCI
+ printk("cpc (bus=0x0%x,pci_id=0x%x,", pdev->bus->number, pdev->devfn);
+ printk("rev_id=%d) IRQ%d\n", cpc_rev_id, card->hw.irq);
+ printk("cpc:found ramaddr=0x%08lx plxaddr=0x%08lx "
+ "ctladdr=0x%08lx falcaddr=0x%08lx\n",
+ card->hw.ramphys, card->hw.plxphys, card->hw.scaphys,
+ card->hw.falcphys);
+#endif
+ /* Although we don't use this I/O region, we should
+ * request it from the kernel anyway, to avoid problems
+ * with other drivers accessing it. */
+ if (!request_region(card->hw.iophys, card->hw.iosize, "PLX Registers")) {
+ /* In case we can't allocate it, warn user */
+ printk("WARNING: couldn't allocate I/O region for PC300 board "
+ "at 0x%08x!\n", card->hw.ramphys);
+ }
+
+ if (card->hw.plxphys) {
+ pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, card->hw.plxphys);
+ } else {
+ eeprom_outdated = 1;
+ card->hw.plxphys = pci_resource_start(pdev, 0);
+ card->hw.plxsize = pci_resource_len(pdev, 0);
+ }
+
+ if (!request_mem_region(card->hw.plxphys, card->hw.plxsize,
+ "PLX Registers")) {
+ printk("PC300 found at RAM 0x%08x, "
+ "but could not allocate PLX mem region.\n",
+ card->hw.ramphys);
+ err = -ENODEV;
+ goto err_release_io;
+ }
+ if (!request_mem_region(card->hw.ramphys, card->hw.alloc_ramsize,
+ "On-board RAM")) {
+ printk("PC300 found at RAM 0x%08x, "
+ "but could not allocate RAM mem region.\n",
+ card->hw.ramphys);
+ err = -ENODEV;
+ goto err_release_plx;
+ }
+ if (!request_mem_region(card->hw.scaphys, card->hw.scasize,
+ "SCA-II Registers")) {
+ printk("PC300 found at RAM 0x%08x, "
+ "but could not allocate SCA mem region.\n",
+ card->hw.ramphys);
+ err = -ENODEV;
+ goto err_release_ram;
+ }
+
+ if ((err = pci_enable_device(pdev)) != 0)
+ goto err_release_sca;
+
+ card->hw.plxbase = ioremap(card->hw.plxphys, card->hw.plxsize);
+ card->hw.rambase = ioremap(card->hw.ramphys, card->hw.alloc_ramsize);
+ card->hw.scabase = ioremap(card->hw.scaphys, card->hw.scasize);
+ switch (device_id) {
+ case PCI_DEVICE_ID_PC300_TE_1:
+ case PCI_DEVICE_ID_PC300_TE_2:
+ case PCI_DEVICE_ID_PC300_TE_M_1:
+ case PCI_DEVICE_ID_PC300_TE_M_2:
+ request_mem_region(card->hw.falcphys, card->hw.falcsize,
+ "FALC Registers");
+ card->hw.falcbase = ioremap(card->hw.falcphys, card->hw.falcsize);
+ break;
+
+ case PCI_DEVICE_ID_PC300_RX_1:
+ case PCI_DEVICE_ID_PC300_RX_2:
+ default:
+ card->hw.falcbase = NULL;
+ break;
+ }
+
+#ifdef PC300_DEBUG_PCI
+ printk("cpc: relocate ramaddr=0x%08lx plxaddr=0x%08lx "
+ "ctladdr=0x%08lx falcaddr=0x%08lx\n",
+ card->hw.rambase, card->hw.plxbase, card->hw.scabase,
+ card->hw.falcbase);
+#endif
+
+ /* Set PCI drv pointer to the card structure */
+ pci_set_drvdata(pdev, card);
+
+ /* Set board type */
+ switch (device_id) {
+ case PCI_DEVICE_ID_PC300_TE_1:
+ case PCI_DEVICE_ID_PC300_TE_2:
+ case PCI_DEVICE_ID_PC300_TE_M_1:
+ case PCI_DEVICE_ID_PC300_TE_M_2:
+ card->hw.type = PC300_TE;
+
+ if ((device_id == PCI_DEVICE_ID_PC300_TE_M_1) ||
+ (device_id == PCI_DEVICE_ID_PC300_TE_M_2)) {
+ card->hw.bus = PC300_PMC;
+ /* Set PLX register offsets */
+ card->hw.gpioc_reg = 0x54;
+ card->hw.intctl_reg = 0x4c;
+ } else {
+ card->hw.bus = PC300_PCI;
+ /* Set PLX register offsets */
+ card->hw.gpioc_reg = 0x50;
+ card->hw.intctl_reg = 0x4c;
+ }
+ break;
+
+ case PCI_DEVICE_ID_PC300_RX_1:
+ case PCI_DEVICE_ID_PC300_RX_2:
+ default:
+ card->hw.bus = PC300_PCI;
+ /* Set PLX register offsets */
+ card->hw.gpioc_reg = 0x50;
+ card->hw.intctl_reg = 0x4c;
+
+ if ((cpc_readl(card->hw.plxbase + card->hw.gpioc_reg) & PC300_CTYPE_MASK)) {
+ card->hw.type = PC300_X21;
+ } else {
+ card->hw.type = PC300_RSV;
+ }
+ break;
+ }
+
+ /* Allocate IRQ */
+ if (request_irq(card->hw.irq, cpc_intr, SA_SHIRQ, "Cyclades-PC300", card)) {
+ printk ("PC300 found at RAM 0x%08x, but could not allocate IRQ%d.\n",
+ card->hw.ramphys, card->hw.irq);
+ goto err_io_unmap;
+ }
+
+ cpc_init_card(card);
+
+ if (eeprom_outdated)
+ printk("WARNING: PC300 with outdated EEPROM.\n");
+ return 0;
+
+err_io_unmap:
+ iounmap(card->hw.plxbase);
+ iounmap(card->hw.scabase);
+ iounmap(card->hw.rambase);
+ if (card->hw.type == PC300_TE) {
+ iounmap(card->hw.falcbase);
+ release_mem_region(card->hw.falcphys, card->hw.falcsize);
+ }
+err_release_sca:
+ release_mem_region(card->hw.scaphys, card->hw.scasize);
+err_release_ram:
+ release_mem_region(card->hw.ramphys, card->hw.alloc_ramsize);
+err_release_plx:
+ release_mem_region(card->hw.plxphys, card->hw.plxsize);
+err_release_io:
+ release_region(card->hw.iophys, card->hw.iosize);
+ kfree(card);
+ return -ENODEV;
+}
+
+static void __devexit cpc_remove_one(struct pci_dev *pdev)
+{
+ pc300_t *card = pci_get_drvdata(pdev);
+
+ if (card->hw.rambase != 0) {
+ int i;
+
+ /* Disable interrupts on the PCI bridge */
+ cpc_writew(card->hw.plxbase + card->hw.intctl_reg,
+ cpc_readw(card->hw.plxbase + card->hw.intctl_reg) & ~(0x0040));
+
+ for (i = 0; i < card->hw.nchan; i++) {
+ unregister_hdlc_device(card->chan[i].d.dev);
+ }
+ iounmap(card->hw.plxbase);
+ iounmap(card->hw.scabase);
+ iounmap(card->hw.rambase);
+ release_mem_region(card->hw.plxphys, card->hw.plxsize);
+ release_mem_region(card->hw.ramphys, card->hw.alloc_ramsize);
+ release_mem_region(card->hw.scaphys, card->hw.scasize);
+ release_region(card->hw.iophys, card->hw.iosize);
+ if (card->hw.type == PC300_TE) {
+ iounmap(card->hw.falcbase);
+ release_mem_region(card->hw.falcphys, card->hw.falcsize);
+ }
+ for (i = 0; i < card->hw.nchan; i++)
+ if (card->chan[i].d.dev)
+ free_netdev(card->chan[i].d.dev);
+ if (card->hw.irq)
+ free_irq(card->hw.irq, card);
+ kfree(card);
+ }
+}
+
+static struct pci_driver cpc_driver = {
+ .name = "pc300",
+ .id_table = cpc_pci_dev_id,
+ .probe = cpc_init_one,
+ .remove = __devexit_p(cpc_remove_one),
+};
+
+static int __init cpc_init(void)
+{
+ return pci_module_init(&cpc_driver);
+}
+
+static void __exit cpc_cleanup_module(void)
+{
+ pci_unregister_driver(&cpc_driver);
+}
+
+module_init(cpc_init);
+module_exit(cpc_cleanup_module);
+
+MODULE_DESCRIPTION("Cyclades-PC300 cards driver");
+MODULE_AUTHOR( "Author: Ivan Passos <ivan@cyclades.com>\r\n"
+ "Maintainer: PC300 Maintainer <pc300@cyclades.com");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/wan/pc300_tty.c b/drivers/net/wan/pc300_tty.c
new file mode 100644
index 00000000000..29f84ad0873
--- /dev/null
+++ b/drivers/net/wan/pc300_tty.c
@@ -0,0 +1,1095 @@
+/*
+ * pc300_tty.c Cyclades-PC300(tm) TTY Driver.
+ *
+ * Author: Regina Kodato <reginak@cyclades.com>
+ *
+ * Copyright: (c) 1999-2002 Cyclades Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Log: pc300_tty.c,v $
+ * Revision 3.7 2002/03/07 14:17:09 henrique
+ * License data fixed
+ *
+ * Revision 3.6 2001/12/10 12:29:42 regina
+ * Fix the MLPPP bug
+ *
+ * Revision 3.5 2001/10/31 11:20:05 regina
+ * automatic pppd starts
+ *
+ * Revision 3.4 2001/08/06 12:01:51 regina
+ * problem in DSR_DE bit
+ *
+ * Revision 3.3 2001/07/26 22:58:41 regina
+ * update EDA value
+ *
+ * Revision 3.2 2001/07/12 13:11:20 regina
+ * bug fix - DCD-OFF in pc300 tty driver
+ *
+ * DMA transmission bug fix
+ *
+ * Revision 3.1 2001/06/22 13:13:02 regina
+ * MLPPP implementation
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/if.h>
+#include <linux/skbuff.h>
+/* TTY includes */
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "pc300.h"
+
+/* defines and macros */
+/* TTY Global definitions */
+#define CPC_TTY_NPORTS 8 /* maximum number of the sync tty connections */
+#define CPC_TTY_MAJOR CYCLADES_MAJOR
+#define CPC_TTY_MINOR_START 240 /* minor of the first PC300 interface */
+
+#define CPC_TTY_MAX_MTU 2000
+
+/* tty interface state */
+#define CPC_TTY_ST_IDLE 0
+#define CPC_TTY_ST_INIT 1 /* configured with MLPPP and up */
+#define CPC_TTY_ST_OPEN 2 /* opened by application */
+
+#define CPC_TTY_LOCK(card,flags)\
+ do {\
+ spin_lock_irqsave(&card->card_lock, flags); \
+ } while (0)
+
+#define CPC_TTY_UNLOCK(card,flags) \
+ do {\
+ spin_unlock_irqrestore(&card->card_lock, flags); \
+ } while (0)
+
+//#define CPC_TTY_DBG(format,a...) printk(format,##a)
+#define CPC_TTY_DBG(format,a...)
+
+/* data structures */
+typedef struct _st_cpc_rx_buf {
+ struct _st_cpc_rx_buf *next;
+ int size;
+ unsigned char data[1];
+} st_cpc_rx_buf;
+
+struct st_cpc_rx_list {
+ st_cpc_rx_buf *first;
+ st_cpc_rx_buf *last;
+};
+
+typedef struct _st_cpc_tty_area {
+ int state; /* state of the TTY interface */
+ int num_open;
+ unsigned int tty_minor; /* minor this interface */
+ volatile struct st_cpc_rx_list buf_rx; /* ptr. to reception buffer */
+ unsigned char* buf_tx; /* ptr. to transmission buffer */
+ pc300dev_t* pc300dev; /* ptr. to info struct in PC300 driver */
+ unsigned char name[20]; /* interf. name + "-tty" */
+ struct tty_struct *tty;
+ struct work_struct tty_tx_work; /* tx work - tx interrupt */
+ struct work_struct tty_rx_work; /* rx work - rx interrupt */
+ } st_cpc_tty_area;
+
+/* TTY data structures */
+static struct tty_driver serial_drv;
+
+/* local variables */
+st_cpc_tty_area cpc_tty_area[CPC_TTY_NPORTS];
+
+int cpc_tty_cnt=0; /* number of intrfaces configured with MLPPP */
+int cpc_tty_unreg_flag = 0;
+
+/* TTY functions prototype */
+static int cpc_tty_open(struct tty_struct *tty, struct file *flip);
+static void cpc_tty_close(struct tty_struct *tty, struct file *flip);
+static int cpc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static int cpc_tty_write_room(struct tty_struct *tty);
+static int cpc_tty_chars_in_buffer(struct tty_struct *tty);
+static void cpc_tty_flush_buffer(struct tty_struct *tty);
+static void cpc_tty_hangup(struct tty_struct *tty);
+static void cpc_tty_rx_work(void *data);
+static void cpc_tty_tx_work(void *data);
+static int cpc_tty_send_to_card(pc300dev_t *dev,void *buf, int len);
+static void cpc_tty_trace(pc300dev_t *dev, char* buf, int len, char rxtx);
+static void cpc_tty_signal_off(pc300dev_t *pc300dev, unsigned char);
+static void cpc_tty_signal_on(pc300dev_t *pc300dev, unsigned char);
+
+int pc300_tiocmset(struct tty_struct *, struct file *,
+ unsigned int, unsigned int);
+int pc300_tiocmget(struct tty_struct *, struct file *);
+
+/* functions called by PC300 driver */
+void cpc_tty_init(pc300dev_t *dev);
+void cpc_tty_unregister_service(pc300dev_t *pc300dev);
+void cpc_tty_receive(pc300dev_t *pc300dev);
+void cpc_tty_trigger_poll(pc300dev_t *pc300dev);
+void cpc_tty_reset_var(void);
+
+/*
+ * PC300 TTY clear "signal"
+ */
+static void cpc_tty_signal_off(pc300dev_t *pc300dev, unsigned char signal)
+{
+ pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan;
+ pc300_t *card = (pc300_t *) pc300chan->card;
+ int ch = pc300chan->channel;
+ unsigned long flags;
+
+ CPC_TTY_DBG("%s-tty: Clear signal %x\n",
+ pc300dev->dev->name, signal);
+ CPC_TTY_LOCK(card, flags);
+ cpc_writeb(card->hw.scabase + M_REG(CTL,ch),
+ cpc_readb(card->hw.scabase+M_REG(CTL,ch))& signal);
+ CPC_TTY_UNLOCK(card,flags);
+}
+
+/*
+ * PC300 TTY set "signal" to ON
+ */
+static void cpc_tty_signal_on(pc300dev_t *pc300dev, unsigned char signal)
+{
+ pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan;
+ pc300_t *card = (pc300_t *) pc300chan->card;
+ int ch = pc300chan->channel;
+ unsigned long flags;
+
+ CPC_TTY_DBG("%s-tty: Set signal %x\n",
+ pc300dev->dev->name, signal);
+ CPC_TTY_LOCK(card, flags);
+ cpc_writeb(card->hw.scabase + M_REG(CTL,ch),
+ cpc_readb(card->hw.scabase+M_REG(CTL,ch))& ~signal);
+ CPC_TTY_UNLOCK(card,flags);
+}
+
+/*
+ * PC300 TTY initialization routine
+ *
+ * This routine is called by the PC300 driver during board configuration
+ * (ioctl=SIOCSP300CONF). At this point the adapter is completely
+ * initialized.
+ * o verify kernel version (only 2.4.x)
+ * o register TTY driver
+ * o init cpc_tty_area struct
+ */
+void cpc_tty_init(pc300dev_t *pc300dev)
+{
+ unsigned long port;
+ int aux;
+ st_cpc_tty_area * cpc_tty;
+
+ /* hdlcX - X=interface number */
+ port = pc300dev->dev->name[4] - '0';
+ if (port >= CPC_TTY_NPORTS) {
+ printk("%s-tty: invalid interface selected (0-%i): %li",
+ pc300dev->dev->name,
+ CPC_TTY_NPORTS-1,port);
+ return;
+ }
+
+ if (cpc_tty_cnt == 0) { /* first TTY connection -> register driver */
+ CPC_TTY_DBG("%s-tty: driver init, major:%i, minor range:%i=%i\n",
+ pc300dev->dev->name,
+ CPC_TTY_MAJOR, CPC_TTY_MINOR_START,
+ CPC_TTY_MINOR_START+CPC_TTY_NPORTS);
+ /* initialize tty driver struct */
+ memset(&serial_drv,0,sizeof(struct tty_driver));
+ serial_drv.magic = TTY_DRIVER_MAGIC;
+ serial_drv.owner = THIS_MODULE;
+ serial_drv.driver_name = "pc300_tty";
+ serial_drv.name = "ttyCP";
+ serial_drv.major = CPC_TTY_MAJOR;
+ serial_drv.minor_start = CPC_TTY_MINOR_START;
+ serial_drv.num = CPC_TTY_NPORTS;
+ serial_drv.type = TTY_DRIVER_TYPE_SERIAL;
+ serial_drv.subtype = SERIAL_TYPE_NORMAL;
+
+ serial_drv.init_termios = tty_std_termios;
+ serial_drv.init_termios.c_cflag = B9600|CS8|CREAD|HUPCL|CLOCAL;
+ serial_drv.flags = TTY_DRIVER_REAL_RAW;
+
+ /* interface routines from the upper tty layer to the tty driver */
+ serial_drv.open = cpc_tty_open;
+ serial_drv.close = cpc_tty_close;
+ serial_drv.write = cpc_tty_write;
+ serial_drv.write_room = cpc_tty_write_room;
+ serial_drv.chars_in_buffer = cpc_tty_chars_in_buffer;
+ serial_drv.tiocmset = pc300_tiocmset;
+ serial_drv.tiocmget = pc300_tiocmget;
+ serial_drv.flush_buffer = cpc_tty_flush_buffer;
+ serial_drv.hangup = cpc_tty_hangup;
+
+ /* register the TTY driver */
+ if (tty_register_driver(&serial_drv)) {
+ printk("%s-tty: Failed to register serial driver! ",
+ pc300dev->dev->name);
+ return;
+ }
+
+ memset((void *)cpc_tty_area, 0,
+ sizeof(st_cpc_tty_area) * CPC_TTY_NPORTS);
+ }
+
+ cpc_tty = &cpc_tty_area[port];
+
+ if (cpc_tty->state != CPC_TTY_ST_IDLE) {
+ CPC_TTY_DBG("%s-tty: TTY port %i, already in use.\n",
+ pc300dev->dev->name, port);
+ return;
+ }
+
+ cpc_tty_cnt++;
+ cpc_tty->state = CPC_TTY_ST_INIT;
+ cpc_tty->num_open= 0;
+ cpc_tty->tty_minor = port + CPC_TTY_MINOR_START;
+ cpc_tty->pc300dev = pc300dev;
+
+ INIT_WORK(&cpc_tty->tty_tx_work, cpc_tty_tx_work, (void *)cpc_tty);
+ INIT_WORK(&cpc_tty->tty_rx_work, cpc_tty_rx_work, (void *)port);
+
+ cpc_tty->buf_rx.first = cpc_tty->buf_rx.last = NULL;
+
+ pc300dev->cpc_tty = (void *)cpc_tty;
+
+ aux = strlen(pc300dev->dev->name);
+ memcpy(cpc_tty->name, pc300dev->dev->name, aux);
+ memcpy(&cpc_tty->name[aux], "-tty", 5);
+
+ cpc_open(pc300dev->dev);
+ cpc_tty_signal_off(pc300dev, CTL_DTR);
+
+ CPC_TTY_DBG("%s: Initializing TTY Sync Driver, tty major#%d minor#%i\n",
+ cpc_tty->name,CPC_TTY_MAJOR,cpc_tty->tty_minor);
+ return;
+}
+
+/*
+ * PC300 TTY OPEN routine
+ *
+ * This routine is called by the tty driver to open the interface
+ * o verify minor
+ * o allocate buffer to Rx and Tx
+ */
+static int cpc_tty_open(struct tty_struct *tty, struct file *flip)
+{
+ int port ;
+ st_cpc_tty_area *cpc_tty;
+
+ if (!tty) {
+ return -ENODEV;
+ }
+
+ port = tty->index;
+
+ if ((port < 0) || (port >= CPC_TTY_NPORTS)){
+ CPC_TTY_DBG("pc300_tty: open invalid port %d\n", port);
+ return -ENODEV;
+ }
+
+ cpc_tty = &cpc_tty_area[port];
+
+ if (cpc_tty->state == CPC_TTY_ST_IDLE){
+ CPC_TTY_DBG("%s: open - invalid interface, port=%d\n",
+ cpc_tty->name, tty->index);
+ return -ENODEV;
+ }
+
+ if (cpc_tty->num_open == 0) { /* first open of this tty */
+ if (!cpc_tty_area[port].buf_tx){
+ cpc_tty_area[port].buf_tx = kmalloc(CPC_TTY_MAX_MTU,GFP_KERNEL);
+ if (cpc_tty_area[port].buf_tx == 0){
+ CPC_TTY_DBG("%s: error in memory allocation\n",cpc_tty->name);
+ return -ENOMEM;
+ }
+ }
+
+ if (cpc_tty_area[port].buf_rx.first) {
+ unsigned char * aux;
+ while (cpc_tty_area[port].buf_rx.first) {
+ aux = (unsigned char *)cpc_tty_area[port].buf_rx.first;
+ cpc_tty_area[port].buf_rx.first = cpc_tty_area[port].buf_rx.first->next;
+ kfree(aux);
+ }
+ cpc_tty_area[port].buf_rx.first = NULL;
+ cpc_tty_area[port].buf_rx.last = NULL;
+ }
+
+ cpc_tty_area[port].state = CPC_TTY_ST_OPEN;
+ cpc_tty_area[port].tty = tty;
+ tty->driver_data = &cpc_tty_area[port];
+
+ cpc_tty_signal_on(cpc_tty->pc300dev, CTL_DTR);
+ }
+
+ cpc_tty->num_open++;
+
+ CPC_TTY_DBG("%s: opening TTY driver\n", cpc_tty->name);
+
+ /* avisar driver PC300 */
+ return 0;
+}
+
+/*
+ * PC300 TTY CLOSE routine
+ *
+ * This routine is called by the tty driver to close the interface
+ * o call close channel in PC300 driver (cpc_closech)
+ * o free Rx and Tx buffers
+ */
+
+static void cpc_tty_close(struct tty_struct *tty, struct file *flip)
+{
+ st_cpc_tty_area *cpc_tty;
+ unsigned long flags;
+ int res;
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlx-tty: no TTY in close \n");
+ return;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if ((cpc_tty->tty != tty)|| (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+ CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+ return;
+ }
+
+ if (!cpc_tty->num_open) {
+ CPC_TTY_DBG("%s: TTY is closed\n",cpc_tty->name);
+ return;
+ }
+
+ if (--cpc_tty->num_open > 0) {
+ CPC_TTY_DBG("%s: TTY closed\n",cpc_tty->name);
+ return;
+ }
+
+ cpc_tty_signal_off(cpc_tty->pc300dev, CTL_DTR);
+
+ CPC_TTY_LOCK(cpc_tty->pc300dev->chan->card, flags); /* lock irq */
+ cpc_tty->tty = NULL;
+ cpc_tty->state = CPC_TTY_ST_INIT;
+ CPC_TTY_UNLOCK(cpc_tty->pc300dev->chan->card, flags); /* unlock irq */
+
+ if (cpc_tty->buf_rx.first) {
+ unsigned char * aux;
+ while (cpc_tty->buf_rx.first) {
+ aux = (unsigned char *)cpc_tty->buf_rx.first;
+ cpc_tty->buf_rx.first = cpc_tty->buf_rx.first->next;
+ kfree(aux);
+ }
+ cpc_tty->buf_rx.first = NULL;
+ cpc_tty->buf_rx.last = NULL;
+ }
+
+ if (cpc_tty->buf_tx) {
+ kfree(cpc_tty->buf_tx);
+ cpc_tty->buf_tx = NULL;
+ }
+
+ CPC_TTY_DBG("%s: TTY closed\n",cpc_tty->name);
+
+ if (!serial_drv.refcount && cpc_tty_unreg_flag) {
+ cpc_tty_unreg_flag = 0;
+ CPC_TTY_DBG("%s: unregister the tty driver\n", cpc_tty->name);
+ if ((res=tty_unregister_driver(&serial_drv))) {
+ CPC_TTY_DBG("%s: ERROR ->unregister the tty driver error=%d\n",
+ cpc_tty->name,res);
+ }
+ }
+ return;
+}
+
+/*
+ * PC300 TTY WRITE routine
+ *
+ * This routine is called by the tty driver to write a series of characters
+ * to the tty device. The characters may come from user or kernel space.
+ * o verify the DCD signal
+ * o send characters to board and start the transmission
+ */
+static int cpc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ st_cpc_tty_area *cpc_tty;
+ pc300ch_t *pc300chan;
+ pc300_t *card;
+ int ch;
+ unsigned long flags;
+ struct net_device_stats *stats;
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlcX-tty: no TTY in write\n");
+ return -ENODEV;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+ CPC_TTY_DBG("%s: TTY is not opened\n", cpc_tty->name);
+ return -ENODEV;
+ }
+
+ if (count > CPC_TTY_MAX_MTU) {
+ CPC_TTY_DBG("%s: count is invalid\n",cpc_tty->name);
+ return -EINVAL; /* frame too big */
+ }
+
+ CPC_TTY_DBG("%s: cpc_tty_write data len=%i\n",cpc_tty->name,count);
+
+ pc300chan = (pc300ch_t *)((pc300dev_t*)cpc_tty->pc300dev)->chan;
+ stats = hdlc_stats(((pc300dev_t*)cpc_tty->pc300dev)->dev);
+ card = (pc300_t *) pc300chan->card;
+ ch = pc300chan->channel;
+
+ /* verify DCD signal*/
+ if (cpc_readb(card->hw.scabase + M_REG(ST3,ch)) & ST3_DCD) {
+ /* DCD is OFF */
+ CPC_TTY_DBG("%s : DCD is OFF\n", cpc_tty->name);
+ stats->tx_errors++;
+ stats->tx_carrier_errors++;
+ CPC_TTY_LOCK(card, flags);
+ cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_BUF_CLR);
+
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) &
+ ~(CPLD_REG2_FALC_LED1 << (2 *ch)));
+ }
+
+ CPC_TTY_UNLOCK(card, flags);
+
+ return -EINVAL;
+ }
+
+ if (cpc_tty_send_to_card(cpc_tty->pc300dev, (void*)buf, count)) {
+ /* failed to send */
+ CPC_TTY_DBG("%s: trasmition error\n", cpc_tty->name);
+ return 0;
+ }
+ return count;
+}
+
+/*
+ * PC300 TTY Write Room routine
+ *
+ * This routine returns the numbers of characteres the tty driver will accept
+ * for queuing to be written.
+ * o return MTU
+ */
+static int cpc_tty_write_room(struct tty_struct *tty)
+{
+ st_cpc_tty_area *cpc_tty;
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlcX-tty: no TTY to write room\n");
+ return -ENODEV;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+ CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+ return -ENODEV;
+ }
+
+ CPC_TTY_DBG("%s: write room\n",cpc_tty->name);
+
+ return CPC_TTY_MAX_MTU;
+}
+
+/*
+ * PC300 TTY chars in buffer routine
+ *
+ * This routine returns the chars number in the transmission buffer
+ * o returns 0
+ */
+static int cpc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ st_cpc_tty_area *cpc_tty;
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlcX-tty: no TTY to chars in buffer\n");
+ return -ENODEV;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+ CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+ return -ENODEV;
+ }
+
+ return(0);
+}
+
+int pc300_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ st_cpc_tty_area *cpc_tty;
+
+ CPC_TTY_DBG("%s: set:%x clear:%x\n", __FUNCTION__, set, clear);
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlcX-tty: no TTY to chars in buffer\n");
+ return -ENODEV;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if (set & TIOCM_RTS)
+ cpc_tty_signal_on(cpc_tty->pc300dev, CTL_RTS);
+ if (set & TIOCM_DTR)
+ cpc_tty_signal_on(cpc_tty->pc300dev, CTL_DTR);
+
+ if (clear & TIOCM_RTS)
+ cpc_tty_signal_off(cpc_tty->pc300dev, CTL_RTS);
+ if (clear & TIOCM_DTR)
+ cpc_tty_signal_off(cpc_tty->pc300dev, CTL_DTR);
+
+ return 0;
+}
+
+int pc300_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ unsigned int result;
+ unsigned char status;
+ unsigned long flags;
+ st_cpc_tty_area *cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+ pc300dev_t *pc300dev = cpc_tty->pc300dev;
+ pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan;
+ pc300_t *card = (pc300_t *) pc300chan->card;
+ int ch = pc300chan->channel;
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ CPC_TTY_DBG("%s-tty: tiocmget\n",
+ ((struct net_device*)(pc300dev->hdlc))->name);
+
+ CPC_TTY_LOCK(card, flags);
+ status = cpc_readb(card->hw.scabase+M_REG(CTL,ch));
+ CPC_TTY_UNLOCK(card,flags);
+
+ result = ((status & CTL_DTR) ? TIOCM_DTR : 0) |
+ ((status & CTL_RTS) ? TIOCM_RTS : 0);
+
+ return result;
+}
+
+/*
+ * PC300 TTY Flush Buffer routine
+ *
+ * This routine resets the transmission buffer
+ */
+static void cpc_tty_flush_buffer(struct tty_struct *tty)
+{
+ st_cpc_tty_area *cpc_tty;
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlcX-tty: no TTY to flush buffer\n");
+ return;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+ CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+ return;
+ }
+
+ CPC_TTY_DBG("%s: call wake_up_interruptible\n",cpc_tty->name);
+
+ tty_wakeup(tty);
+ return;
+}
+
+/*
+ * PC300 TTY Hangup routine
+ *
+ * This routine is called by the tty driver to hangup the interface
+ * o clear DTR signal
+ */
+
+static void cpc_tty_hangup(struct tty_struct *tty)
+{
+ st_cpc_tty_area *cpc_tty;
+ int res;
+
+ if (!tty || !tty->driver_data ) {
+ CPC_TTY_DBG("hdlcX-tty: no TTY to hangup\n");
+ return ;
+ }
+
+ cpc_tty = (st_cpc_tty_area *) tty->driver_data;
+
+ if ((cpc_tty->tty != tty) || (cpc_tty->state != CPC_TTY_ST_OPEN)) {
+ CPC_TTY_DBG("%s: TTY is not opened\n",cpc_tty->name);
+ return ;
+ }
+ if (!serial_drv.refcount && cpc_tty_unreg_flag) {
+ cpc_tty_unreg_flag = 0;
+ CPC_TTY_DBG("%s: unregister the tty driver\n", cpc_tty->name);
+ if ((res=tty_unregister_driver(&serial_drv))) {
+ CPC_TTY_DBG("%s: ERROR ->unregister the tty driver error=%d\n",
+ cpc_tty->name,res);
+ }
+ }
+ cpc_tty_signal_off(cpc_tty->pc300dev, CTL_DTR);
+}
+
+/*
+ * PC300 TTY RX work routine
+ * This routine treats RX work
+ * o verify read buffer
+ * o call the line disc. read
+ * o free memory
+ */
+static void cpc_tty_rx_work(void * data)
+{
+ unsigned long port;
+ int i, j;
+ st_cpc_tty_area *cpc_tty;
+ volatile st_cpc_rx_buf * buf;
+ char flags=0,flg_rx=1;
+ struct tty_ldisc *ld;
+
+ if (cpc_tty_cnt == 0) return;
+
+
+ for (i=0; (i < 4) && flg_rx ; i++) {
+ flg_rx = 0;
+ port = (unsigned long)data;
+ for (j=0; j < CPC_TTY_NPORTS; j++) {
+ cpc_tty = &cpc_tty_area[port];
+
+ if ((buf=cpc_tty->buf_rx.first) != 0) {
+ if(cpc_tty->tty) {
+ ld = tty_ldisc_ref(cpc_tty->tty);
+ if(ld) {
+ if (ld->receive_buf) {
+ CPC_TTY_DBG("%s: call line disc. receive_buf\n",cpc_tty->name);
+ ld->receive_buf(cpc_tty->tty, (char *)(buf->data), &flags, buf->size);
+ }
+ tty_ldisc_deref(ld);
+ }
+ }
+ cpc_tty->buf_rx.first = cpc_tty->buf_rx.first->next;
+ kfree((unsigned char *)buf);
+ buf = cpc_tty->buf_rx.first;
+ flg_rx = 1;
+ }
+ if (++port == CPC_TTY_NPORTS) port = 0;
+ }
+ }
+}
+
+/*
+ * PC300 TTY RX work routine
+ *
+ * This routine treats RX interrupt.
+ * o read all frames in card
+ * o verify the frame size
+ * o read the frame in rx buffer
+ */
+static void cpc_tty_rx_disc_frame(pc300ch_t *pc300chan)
+{
+ volatile pcsca_bd_t __iomem * ptdescr;
+ volatile unsigned char status;
+ pc300_t *card = (pc300_t *)pc300chan->card;
+ int ch = pc300chan->channel;
+
+ /* dma buf read */
+ ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase +
+ RX_BD_ADDR(ch, pc300chan->rx_first_bd));
+ while (pc300chan->rx_first_bd != pc300chan->rx_last_bd) {
+ status = cpc_readb(&ptdescr->status);
+ cpc_writeb(&ptdescr->status, 0);
+ cpc_writeb(&ptdescr->len, 0);
+ pc300chan->rx_first_bd = (pc300chan->rx_first_bd + 1) &
+ (N_DMA_RX_BUF - 1);
+ if (status & DST_EOM) {
+ break; /* end of message */
+ }
+ ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase + cpc_readl(&ptdescr->next));
+ }
+}
+
+void cpc_tty_receive(pc300dev_t *pc300dev)
+{
+ st_cpc_tty_area *cpc_tty;
+ pc300ch_t *pc300chan = (pc300ch_t *)pc300dev->chan;
+ pc300_t *card = (pc300_t *)pc300chan->card;
+ int ch = pc300chan->channel;
+ volatile pcsca_bd_t __iomem * ptdescr;
+ struct net_device_stats *stats = hdlc_stats(pc300dev->dev);
+ int rx_len, rx_aux;
+ volatile unsigned char status;
+ unsigned short first_bd = pc300chan->rx_first_bd;
+ st_cpc_rx_buf *new=NULL;
+ unsigned char dsr_rx;
+
+ if (pc300dev->cpc_tty == NULL) {
+ return;
+ }
+
+ dsr_rx = cpc_readb(card->hw.scabase + DSR_RX(ch));
+
+ cpc_tty = (st_cpc_tty_area *)pc300dev->cpc_tty;
+
+ while (1) {
+ rx_len = 0;
+ ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase + RX_BD_ADDR(ch, first_bd));
+ while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+ rx_len += cpc_readw(&ptdescr->len);
+ first_bd = (first_bd + 1) & (N_DMA_RX_BUF - 1);
+ if (status & DST_EOM) {
+ break;
+ }
+ ptdescr=(pcsca_bd_t __iomem *)(card->hw.rambase+cpc_readl(&ptdescr->next));
+ }
+
+ if (!rx_len) {
+ if (dsr_rx & DSR_BOF) {
+ /* update EDA */
+ cpc_writel(card->hw.scabase + DRX_REG(EDAL, ch),
+ RX_BD_ADDR(ch, pc300chan->rx_last_bd));
+ }
+ if (new) {
+ kfree(new);
+ new = NULL;
+ }
+ return;
+ }
+
+ if (rx_len > CPC_TTY_MAX_MTU) {
+ /* Free RX descriptors */
+ CPC_TTY_DBG("%s: frame size is invalid.\n",cpc_tty->name);
+ stats->rx_errors++;
+ stats->rx_frame_errors++;
+ cpc_tty_rx_disc_frame(pc300chan);
+ continue;
+ }
+
+ new = (st_cpc_rx_buf *) kmalloc(rx_len + sizeof(st_cpc_rx_buf), GFP_ATOMIC);
+ if (new == 0) {
+ cpc_tty_rx_disc_frame(pc300chan);
+ continue;
+ }
+
+ /* dma buf read */
+ ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase +
+ RX_BD_ADDR(ch, pc300chan->rx_first_bd));
+
+ rx_len = 0; /* counter frame size */
+
+ while ((status = cpc_readb(&ptdescr->status)) & DST_OSB) {
+ rx_aux = cpc_readw(&ptdescr->len);
+ if ((status & (DST_OVR | DST_CRC | DST_RBIT | DST_SHRT | DST_ABT))
+ || (rx_aux > BD_DEF_LEN)) {
+ CPC_TTY_DBG("%s: reception error\n", cpc_tty->name);
+ stats->rx_errors++;
+ if (status & DST_OVR) {
+ stats->rx_fifo_errors++;
+ }
+ if (status & DST_CRC) {
+ stats->rx_crc_errors++;
+ }
+ if ((status & (DST_RBIT | DST_SHRT | DST_ABT)) ||
+ (rx_aux > BD_DEF_LEN)) {
+ stats->rx_frame_errors++;
+ }
+ /* discard remainig descriptors used by the bad frame */
+ CPC_TTY_DBG("%s: reception error - discard descriptors",
+ cpc_tty->name);
+ cpc_tty_rx_disc_frame(pc300chan);
+ rx_len = 0;
+ kfree(new);
+ new = NULL;
+ break; /* read next frame - while(1) */
+ }
+
+ if (cpc_tty->state != CPC_TTY_ST_OPEN) {
+ /* Free RX descriptors */
+ cpc_tty_rx_disc_frame(pc300chan);
+ stats->rx_dropped++;
+ rx_len = 0;
+ kfree(new);
+ new = NULL;
+ break; /* read next frame - while(1) */
+ }
+
+ /* read the segment of the frame */
+ if (rx_aux != 0) {
+ memcpy_fromio((new->data + rx_len),
+ (void __iomem *)(card->hw.rambase +
+ cpc_readl(&ptdescr->ptbuf)), rx_aux);
+ rx_len += rx_aux;
+ }
+ cpc_writeb(&ptdescr->status,0);
+ cpc_writeb(&ptdescr->len, 0);
+ pc300chan->rx_first_bd = (pc300chan->rx_first_bd + 1) &
+ (N_DMA_RX_BUF -1);
+ if (status & DST_EOM)break;
+
+ ptdescr = (pcsca_bd_t __iomem *) (card->hw.rambase +
+ cpc_readl(&ptdescr->next));
+ }
+ /* update pointer */
+ pc300chan->rx_last_bd = (pc300chan->rx_first_bd - 1) &
+ (N_DMA_RX_BUF - 1) ;
+ if (!(dsr_rx & DSR_BOF)) {
+ /* update EDA */
+ cpc_writel(card->hw.scabase + DRX_REG(EDAL, ch),
+ RX_BD_ADDR(ch, pc300chan->rx_last_bd));
+ }
+ if (rx_len != 0) {
+ stats->rx_bytes += rx_len;
+
+ if (pc300dev->trace_on) {
+ cpc_tty_trace(pc300dev, new->data,rx_len, 'R');
+ }
+ new->size = rx_len;
+ new->next = NULL;
+ if (cpc_tty->buf_rx.first == 0) {
+ cpc_tty->buf_rx.first = new;
+ cpc_tty->buf_rx.last = new;
+ } else {
+ cpc_tty->buf_rx.last->next = new;
+ cpc_tty->buf_rx.last = new;
+ }
+ schedule_work(&(cpc_tty->tty_rx_work));
+ stats->rx_packets++;
+ }
+ }
+}
+
+/*
+ * PC300 TTY TX work routine
+ *
+ * This routine treats TX interrupt.
+ * o if need call line discipline wakeup
+ * o call wake_up_interruptible
+ */
+static void cpc_tty_tx_work(void *data)
+{
+ st_cpc_tty_area *cpc_tty = (st_cpc_tty_area *) data;
+ struct tty_struct *tty;
+
+ CPC_TTY_DBG("%s: cpc_tty_tx_work init\n",cpc_tty->name);
+
+ if ((tty = cpc_tty->tty) == 0) {
+ CPC_TTY_DBG("%s: the interface is not opened\n",cpc_tty->name);
+ return;
+ }
+ tty_wakeup(tty);
+}
+
+/*
+ * PC300 TTY send to card routine
+ *
+ * This routine send data to card.
+ * o clear descriptors
+ * o write data to DMA buffers
+ * o start the transmission
+ */
+static int cpc_tty_send_to_card(pc300dev_t *dev,void* buf, int len)
+{
+ pc300ch_t *chan = (pc300ch_t *)dev->chan;
+ pc300_t *card = (pc300_t *)chan->card;
+ int ch = chan->channel;
+ struct net_device_stats *stats = hdlc_stats(dev->dev);
+ unsigned long flags;
+ volatile pcsca_bd_t __iomem *ptdescr;
+ int i, nchar;
+ int tosend = len;
+ int nbuf = ((len - 1)/BD_DEF_LEN) + 1;
+ unsigned char *pdata=buf;
+
+ CPC_TTY_DBG("%s:cpc_tty_send_to_cars len=%i",
+ (st_cpc_tty_area *)dev->cpc_tty->name,len);
+
+ if (nbuf >= card->chan[ch].nfree_tx_bd) {
+ return 1;
+ }
+
+ /* write buffer to DMA buffers */
+ CPC_TTY_DBG("%s: call dma_buf_write\n",
+ (st_cpc_tty_area *)dev->cpc_tty->name);
+ for (i = 0 ; i < nbuf ; i++) {
+ ptdescr = (pcsca_bd_t __iomem *)(card->hw.rambase +
+ TX_BD_ADDR(ch, card->chan[ch].tx_next_bd));
+ nchar = (BD_DEF_LEN > tosend) ? tosend : BD_DEF_LEN;
+ if (cpc_readb(&ptdescr->status) & DST_OSB) {
+ memcpy_toio((void __iomem *)(card->hw.rambase +
+ cpc_readl(&ptdescr->ptbuf)),
+ &pdata[len - tosend],
+ nchar);
+ card->chan[ch].nfree_tx_bd--;
+ if ((i + 1) == nbuf) {
+ /* This must be the last BD to be used */
+ cpc_writeb(&ptdescr->status, DST_EOM);
+ } else {
+ cpc_writeb(&ptdescr->status, 0);
+ }
+ cpc_writew(&ptdescr->len, nchar);
+ } else {
+ CPC_TTY_DBG("%s: error in dma_buf_write\n",
+ (st_cpc_tty_area *)dev->cpc_tty->name);
+ stats->tx_dropped++;
+ return 1;
+ }
+ tosend -= nchar;
+ card->chan[ch].tx_next_bd =
+ (card->chan[ch].tx_next_bd + 1) & (N_DMA_TX_BUF - 1);
+ }
+
+ if (dev->trace_on) {
+ cpc_tty_trace(dev, buf, len,'T');
+ }
+
+ /* start transmission */
+ CPC_TTY_DBG("%s: start transmission\n",
+ (st_cpc_tty_area *)dev->cpc_tty->name);
+
+ CPC_TTY_LOCK(card, flags);
+ cpc_writeb(card->hw.scabase + DTX_REG(EDAL, ch),
+ TX_BD_ADDR(ch, chan->tx_next_bd));
+ cpc_writeb(card->hw.scabase + M_REG(CMD, ch), CMD_TX_ENA);
+ cpc_writeb(card->hw.scabase + DSR_TX(ch), DSR_DE);
+
+ if (card->hw.type == PC300_TE) {
+ cpc_writeb(card->hw.falcbase + card->hw.cpld_reg2,
+ cpc_readb(card->hw.falcbase + card->hw.cpld_reg2) |
+ (CPLD_REG2_FALC_LED1 << (2 * ch)));
+ }
+ CPC_TTY_UNLOCK(card, flags);
+ return 0;
+}
+
+/*
+ * PC300 TTY trace routine
+ *
+ * This routine send trace of connection to application.
+ * o clear descriptors
+ * o write data to DMA buffers
+ * o start the transmission
+ */
+
+static void cpc_tty_trace(pc300dev_t *dev, char* buf, int len, char rxtx)
+{
+ struct sk_buff *skb;
+
+ if ((skb = dev_alloc_skb(10 + len)) == NULL) {
+ /* out of memory */
+ CPC_TTY_DBG("%s: tty_trace - out of memory\n", dev->dev->name);
+ return;
+ }
+
+ skb_put (skb, 10 + len);
+ skb->dev = dev->dev;
+ skb->protocol = htons(ETH_P_CUST);
+ skb->mac.raw = skb->data;
+ skb->pkt_type = PACKET_HOST;
+ skb->len = 10 + len;
+
+ memcpy(skb->data,dev->dev->name,5);
+ skb->data[5] = '[';
+ skb->data[6] = rxtx;
+ skb->data[7] = ']';
+ skb->data[8] = ':';
+ skb->data[9] = ' ';
+ memcpy(&skb->data[10], buf, len);
+ netif_rx(skb);
+}
+
+/*
+ * PC300 TTY unregister service routine
+ *
+ * This routine unregister one interface.
+ */
+void cpc_tty_unregister_service(pc300dev_t *pc300dev)
+{
+ st_cpc_tty_area *cpc_tty;
+ ulong flags;
+ int res;
+
+ if ((cpc_tty= (st_cpc_tty_area *) pc300dev->cpc_tty) == 0) {
+ CPC_TTY_DBG("%s: interface is not TTY\n", pc300dev->dev->name);
+ return;
+ }
+ CPC_TTY_DBG("%s: cpc_tty_unregister_service", cpc_tty->name);
+
+ if (cpc_tty->pc300dev != pc300dev) {
+ CPC_TTY_DBG("%s: invalid tty ptr=%s\n",
+ pc300dev->dev->name, cpc_tty->name);
+ return;
+ }
+
+ if (--cpc_tty_cnt == 0) {
+ if (serial_drv.refcount) {
+ CPC_TTY_DBG("%s: unregister is not possible, refcount=%d",
+ cpc_tty->name, serial_drv.refcount);
+ cpc_tty_cnt++;
+ cpc_tty_unreg_flag = 1;
+ return;
+ } else {
+ CPC_TTY_DBG("%s: unregister the tty driver\n", cpc_tty->name);
+ if ((res=tty_unregister_driver(&serial_drv))) {
+ CPC_TTY_DBG("%s: ERROR ->unregister the tty driver error=%d\n",
+ cpc_tty->name,res);
+ }
+ }
+ }
+ CPC_TTY_LOCK(pc300dev->chan->card,flags);
+ cpc_tty->tty = NULL;
+ CPC_TTY_UNLOCK(pc300dev->chan->card, flags);
+ cpc_tty->tty_minor = 0;
+ cpc_tty->state = CPC_TTY_ST_IDLE;
+}
+
+/*
+ * PC300 TTY trigger poll routine
+ * This routine is called by pc300driver to treats Tx interrupt.
+ */
+void cpc_tty_trigger_poll(pc300dev_t *pc300dev)
+{
+ st_cpc_tty_area *cpc_tty = (st_cpc_tty_area *)pc300dev->cpc_tty;
+ if (!cpc_tty) {
+ return;
+ }
+ schedule_work(&(cpc_tty->tty_tx_work));
+}
+
+/*
+ * PC300 TTY reset var routine
+ * This routine is called by pc300driver to init the TTY area.
+ */
+
+void cpc_tty_reset_var(void)
+{
+ int i ;
+
+ CPC_TTY_DBG("hdlcX-tty: reset variables\n");
+ /* reset the tty_driver structure - serial_drv */
+ memset(&serial_drv, 0, sizeof(struct tty_driver));
+ for (i=0; i < CPC_TTY_NPORTS; i++){
+ memset(&cpc_tty_area[i],0, sizeof(st_cpc_tty_area));
+ }
+}
diff --git a/drivers/net/wan/pci200syn.c b/drivers/net/wan/pci200syn.c
new file mode 100644
index 00000000000..8dea07b4799
--- /dev/null
+++ b/drivers/net/wan/pci200syn.c
@@ -0,0 +1,488 @@
+/*
+ * Goramo PCI200SYN synchronous serial card driver for Linux
+ *
+ * Copyright (C) 2002-2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * For information see http://hq.pm.waw.pl/hdlc/
+ *
+ * Sources of information:
+ * Hitachi HD64572 SCA-II User's Manual
+ * PLX Technology Inc. PCI9052 Data Book
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/pci.h>
+#include <asm/delay.h>
+#include <asm/io.h>
+
+#include "hd64572.h"
+
+static const char* version = "Goramo PCI200SYN driver version: 1.16";
+static const char* devname = "PCI200SYN";
+
+#undef DEBUG_PKT
+#define DEBUG_RINGS
+
+#define PCI200SYN_PLX_SIZE 0x80 /* PLX control window size (128b) */
+#define PCI200SYN_SCA_SIZE 0x400 /* SCA window size (1Kb) */
+#define ALL_PAGES_ALWAYS_MAPPED
+#define NEED_DETECT_RAM
+#define NEED_SCA_MSCI_INTR
+#define MAX_TX_BUFFERS 10
+
+static int pci_clock_freq = 33000000;
+#define CLOCK_BASE pci_clock_freq
+
+#define PCI_VENDOR_ID_GORAMO 0x10B5 /* uses PLX:9050 ID - this card */
+#define PCI_DEVICE_ID_PCI200SYN 0x9050 /* doesn't have its own ID */
+
+
+/*
+ * PLX PCI9052 local configuration and shared runtime registers.
+ * This structure can be used to access 9052 registers (memory mapped).
+ */
+typedef struct {
+ u32 loc_addr_range[4]; /* 00-0Ch : Local Address Ranges */
+ u32 loc_rom_range; /* 10h : Local ROM Range */
+ u32 loc_addr_base[4]; /* 14-20h : Local Address Base Addrs */
+ u32 loc_rom_base; /* 24h : Local ROM Base */
+ u32 loc_bus_descr[4]; /* 28-34h : Local Bus Descriptors */
+ u32 rom_bus_descr; /* 38h : ROM Bus Descriptor */
+ u32 cs_base[4]; /* 3C-48h : Chip Select Base Addrs */
+ u32 intr_ctrl_stat; /* 4Ch : Interrupt Control/Status */
+ u32 init_ctrl; /* 50h : EEPROM ctrl, Init Ctrl, etc */
+}plx9052;
+
+
+
+typedef struct port_s {
+ struct net_device *dev;
+ struct card_s *card;
+ spinlock_t lock; /* TX lock */
+ sync_serial_settings settings;
+ int rxpart; /* partial frame received, next frame invalid*/
+ unsigned short encoding;
+ unsigned short parity;
+ u16 rxin; /* rx ring buffer 'in' pointer */
+ u16 txin; /* tx ring buffer 'in' and 'last' pointers */
+ u16 txlast;
+ u8 rxs, txs, tmc; /* SCA registers */
+ u8 phy_node; /* physical port # - 0 or 1 */
+}port_t;
+
+
+
+typedef struct card_s {
+ u8 __iomem *rambase; /* buffer memory base (virtual) */
+ u8 __iomem *scabase; /* SCA memory base (virtual) */
+ plx9052 __iomem *plxbase;/* PLX registers memory base (virtual) */
+ u16 rx_ring_buffers; /* number of buffers in a ring */
+ u16 tx_ring_buffers;
+ u16 buff_offset; /* offset of first buffer of first channel */
+ u8 irq; /* interrupt request level */
+
+ port_t ports[2];
+}card_t;
+
+
+#define sca_in(reg, card) readb(card->scabase + (reg))
+#define sca_out(value, reg, card) writeb(value, card->scabase + (reg))
+#define sca_inw(reg, card) readw(card->scabase + (reg))
+#define sca_outw(value, reg, card) writew(value, card->scabase + (reg))
+#define sca_inl(reg, card) readl(card->scabase + (reg))
+#define sca_outl(value, reg, card) writel(value, card->scabase + (reg))
+
+#define port_to_card(port) (port->card)
+#define log_node(port) (port->phy_node)
+#define phy_node(port) (port->phy_node)
+#define winbase(card) (card->rambase)
+#define get_port(card, port) (&card->ports[port])
+#define sca_flush(card) (sca_in(IER0, card));
+
+static inline void new_memcpy_toio(char __iomem *dest, char *src, int length)
+{
+ int len;
+ do {
+ len = length > 256 ? 256 : length;
+ memcpy_toio(dest, src, len);
+ dest += len;
+ src += len;
+ length -= len;
+ readb(dest);
+ } while (len);
+}
+
+#undef memcpy_toio
+#define memcpy_toio new_memcpy_toio
+
+#include "hd6457x.c"
+
+
+static void pci200_set_iface(port_t *port)
+{
+ card_t *card = port->card;
+ u16 msci = get_msci(port);
+ u8 rxs = port->rxs & CLK_BRG_MASK;
+ u8 txs = port->txs & CLK_BRG_MASK;
+
+ sca_out(EXS_TES1, (phy_node(port) ? MSCI1_OFFSET : MSCI0_OFFSET) + EXS,
+ port_to_card(port));
+ switch(port->settings.clock_type) {
+ case CLOCK_INT:
+ rxs |= CLK_BRG; /* BRG output */
+ txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
+ break;
+
+ case CLOCK_TXINT:
+ rxs |= CLK_LINE; /* RXC input */
+ txs |= CLK_PIN_OUT | CLK_BRG; /* BRG output */
+ break;
+
+ case CLOCK_TXFROMRX:
+ rxs |= CLK_LINE; /* RXC input */
+ txs |= CLK_PIN_OUT | CLK_TX_RXCLK; /* RX clock */
+ break;
+
+ default: /* EXTernal clock */
+ rxs |= CLK_LINE; /* RXC input */
+ txs |= CLK_PIN_OUT | CLK_LINE; /* TXC input */
+ break;
+ }
+
+ port->rxs = rxs;
+ port->txs = txs;
+ sca_out(rxs, msci + RXS, card);
+ sca_out(txs, msci + TXS, card);
+ sca_set_port(port);
+}
+
+
+
+static int pci200_open(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+
+ int result = hdlc_open(dev);
+ if (result)
+ return result;
+
+ sca_open(dev);
+ pci200_set_iface(port);
+ sca_flush(port_to_card(port));
+ return 0;
+}
+
+
+
+static int pci200_close(struct net_device *dev)
+{
+ sca_close(dev);
+ sca_flush(port_to_card(dev_to_port(dev)));
+ hdlc_close(dev);
+ return 0;
+}
+
+
+
+static int pci200_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings new_line;
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ port_t *port = dev_to_port(dev);
+
+#ifdef DEBUG_RINGS
+ if (cmd == SIOCDEVPRIVATE) {
+ sca_dump_rings(dev);
+ return 0;
+ }
+#endif
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ ifr->ifr_settings.type = IF_IFACE_V35;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ if (copy_to_user(line, &port->settings, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_V35:
+ case IF_IFACE_SYNC_SERIAL:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&new_line, line, size))
+ return -EFAULT;
+
+ if (new_line.clock_type != CLOCK_EXT &&
+ new_line.clock_type != CLOCK_TXFROMRX &&
+ new_line.clock_type != CLOCK_INT &&
+ new_line.clock_type != CLOCK_TXINT)
+ return -EINVAL; /* No such clock setting */
+
+ if (new_line.loopback != 0 && new_line.loopback != 1)
+ return -EINVAL;
+
+ memcpy(&port->settings, &new_line, size); /* Update settings */
+ pci200_set_iface(port);
+ sca_flush(port_to_card(port));
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+
+
+static void pci200_pci_remove_one(struct pci_dev *pdev)
+{
+ int i;
+ card_t *card = pci_get_drvdata(pdev);
+
+ for(i = 0; i < 2; i++)
+ if (card->ports[i].card) {
+ struct net_device *dev = port_to_dev(&card->ports[i]);
+ unregister_hdlc_device(dev);
+ }
+
+ if (card->irq)
+ free_irq(card->irq, card);
+
+ if (card->rambase)
+ iounmap(card->rambase);
+ if (card->scabase)
+ iounmap(card->scabase);
+ if (card->plxbase)
+ iounmap(card->plxbase);
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ pci_set_drvdata(pdev, NULL);
+ if (card->ports[0].dev)
+ free_netdev(card->ports[0].dev);
+ if (card->ports[1].dev)
+ free_netdev(card->ports[1].dev);
+ kfree(card);
+}
+
+
+
+static int __devinit pci200_pci_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ card_t *card;
+ u8 rev_id;
+ u32 __iomem *p;
+ int i;
+ u32 ramsize;
+ u32 ramphys; /* buffer memory base */
+ u32 scaphys; /* SCA memory base */
+ u32 plxphys; /* PLX registers memory base */
+
+#ifndef MODULE
+ static int printed_version;
+ if (!printed_version++)
+ printk(KERN_INFO "%s\n", version);
+#endif
+
+ i = pci_enable_device(pdev);
+ if (i)
+ return i;
+
+ i = pci_request_regions(pdev, "PCI200SYN");
+ if (i) {
+ pci_disable_device(pdev);
+ return i;
+ }
+
+ card = kmalloc(sizeof(card_t), GFP_KERNEL);
+ if (card == NULL) {
+ printk(KERN_ERR "pci200syn: unable to allocate memory\n");
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ return -ENOBUFS;
+ }
+ memset(card, 0, sizeof(card_t));
+ pci_set_drvdata(pdev, card);
+ card->ports[0].dev = alloc_hdlcdev(&card->ports[0]);
+ card->ports[1].dev = alloc_hdlcdev(&card->ports[1]);
+ if (!card->ports[0].dev || !card->ports[1].dev) {
+ printk(KERN_ERR "pci200syn: unable to allocate memory\n");
+ pci200_pci_remove_one(pdev);
+ return -ENOMEM;
+ }
+
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id);
+ if (pci_resource_len(pdev, 0) != PCI200SYN_PLX_SIZE ||
+ pci_resource_len(pdev, 2) != PCI200SYN_SCA_SIZE ||
+ pci_resource_len(pdev, 3) < 16384) {
+ printk(KERN_ERR "pci200syn: invalid card EEPROM parameters\n");
+ pci200_pci_remove_one(pdev);
+ return -EFAULT;
+ }
+
+ plxphys = pci_resource_start(pdev,0) & PCI_BASE_ADDRESS_MEM_MASK;
+ card->plxbase = ioremap(plxphys, PCI200SYN_PLX_SIZE);
+
+ scaphys = pci_resource_start(pdev,2) & PCI_BASE_ADDRESS_MEM_MASK;
+ card->scabase = ioremap(scaphys, PCI200SYN_SCA_SIZE);
+
+ ramphys = pci_resource_start(pdev,3) & PCI_BASE_ADDRESS_MEM_MASK;
+ card->rambase = ioremap(ramphys, pci_resource_len(pdev,3));
+
+ if (card->plxbase == NULL ||
+ card->scabase == NULL ||
+ card->rambase == NULL) {
+ printk(KERN_ERR "pci200syn: ioremap() failed\n");
+ pci200_pci_remove_one(pdev);
+ }
+
+ /* Reset PLX */
+ p = &card->plxbase->init_ctrl;
+ writel(readl(p) | 0x40000000, p);
+ readl(p); /* Flush the write - do not use sca_flush */
+ udelay(1);
+
+ writel(readl(p) & ~0x40000000, p);
+ readl(p); /* Flush the write - do not use sca_flush */
+ udelay(1);
+
+ ramsize = sca_detect_ram(card, card->rambase,
+ pci_resource_len(pdev, 3));
+
+ /* number of TX + RX buffers for one port - this is dual port card */
+ i = ramsize / (2 * (sizeof(pkt_desc) + HDLC_MAX_MRU));
+ card->tx_ring_buffers = min(i / 2, MAX_TX_BUFFERS);
+ card->rx_ring_buffers = i - card->tx_ring_buffers;
+
+ card->buff_offset = 2 * sizeof(pkt_desc) * (card->tx_ring_buffers +
+ card->rx_ring_buffers);
+
+ printk(KERN_INFO "pci200syn: %u KB RAM at 0x%x, IRQ%u, using %u TX +"
+ " %u RX packets rings\n", ramsize / 1024, ramphys,
+ pdev->irq, card->tx_ring_buffers, card->rx_ring_buffers);
+
+ if (card->tx_ring_buffers < 1) {
+ printk(KERN_ERR "pci200syn: RAM test failed\n");
+ pci200_pci_remove_one(pdev);
+ return -EFAULT;
+ }
+
+ /* Enable interrupts on the PCI bridge */
+ p = &card->plxbase->intr_ctrl_stat;
+ writew(readw(p) | 0x0040, p);
+
+ /* Allocate IRQ */
+ if(request_irq(pdev->irq, sca_intr, SA_SHIRQ, devname, card)) {
+ printk(KERN_WARNING "pci200syn: could not allocate IRQ%d.\n",
+ pdev->irq);
+ pci200_pci_remove_one(pdev);
+ return -EBUSY;
+ }
+ card->irq = pdev->irq;
+
+ sca_init(card, 0);
+
+ for(i = 0; i < 2; i++) {
+ port_t *port = &card->ports[i];
+ struct net_device *dev = port_to_dev(port);
+ hdlc_device *hdlc = dev_to_hdlc(dev);
+ port->phy_node = i;
+
+ spin_lock_init(&port->lock);
+ SET_MODULE_OWNER(dev);
+ dev->irq = card->irq;
+ dev->mem_start = ramphys;
+ dev->mem_end = ramphys + ramsize - 1;
+ dev->tx_queue_len = 50;
+ dev->do_ioctl = pci200_ioctl;
+ dev->open = pci200_open;
+ dev->stop = pci200_close;
+ hdlc->attach = sca_attach;
+ hdlc->xmit = sca_xmit;
+ port->settings.clock_type = CLOCK_EXT;
+ port->card = card;
+ if(register_hdlc_device(dev)) {
+ printk(KERN_ERR "pci200syn: unable to register hdlc "
+ "device\n");
+ port->card = NULL;
+ pci200_pci_remove_one(pdev);
+ return -ENOBUFS;
+ }
+ sca_init_sync_port(port); /* Set up SCA memory */
+
+ printk(KERN_INFO "%s: PCI200SYN node %d\n",
+ dev->name, port->phy_node);
+ }
+
+ sca_flush(card);
+ return 0;
+}
+
+
+
+static struct pci_device_id pci200_pci_tbl[] __devinitdata = {
+ { PCI_VENDOR_ID_GORAMO, PCI_DEVICE_ID_PCI200SYN, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, 0 },
+ { 0, }
+};
+
+
+static struct pci_driver pci200_pci_driver = {
+ .name = "PCI200SYN",
+ .id_table = pci200_pci_tbl,
+ .probe = pci200_pci_init_one,
+ .remove = pci200_pci_remove_one,
+};
+
+
+static int __init pci200_init_module(void)
+{
+#ifdef MODULE
+ printk(KERN_INFO "%s\n", version);
+#endif
+ if (pci_clock_freq < 1000000 || pci_clock_freq > 80000000) {
+ printk(KERN_ERR "pci200syn: Invalid PCI clock frequency\n");
+ return -EINVAL;
+ }
+ return pci_module_init(&pci200_pci_driver);
+}
+
+
+
+static void __exit pci200_cleanup_module(void)
+{
+ pci_unregister_driver(&pci200_pci_driver);
+}
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("Goramo PCI200SYN serial port driver");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(pci, pci200_pci_tbl);
+module_param(pci_clock_freq, int, 0444);
+MODULE_PARM_DESC(pci_clock_freq, "System PCI clock frequency in Hz");
+module_init(pci200_init_module);
+module_exit(pci200_cleanup_module);
diff --git a/drivers/net/wan/sbni.c b/drivers/net/wan/sbni.c
new file mode 100644
index 00000000000..db2c798ba89
--- /dev/null
+++ b/drivers/net/wan/sbni.c
@@ -0,0 +1,1735 @@
+/* sbni.c: Granch SBNI12 leased line adapters driver for linux
+ *
+ * Written 2001 by Denis I.Timofeev (timofeev@granch.ru)
+ *
+ * Previous versions were written by Yaroslav Polyakov,
+ * Alexey Zverev and Max Khon.
+ *
+ * Driver supports SBNI12-02,-04,-05,-10,-11 cards, single and
+ * double-channel, PCI and ISA modifications.
+ * More info and useful utilities to work with SBNI12 cards you can find
+ * at http://www.granch.com (English) or http://www.granch.ru (Russian)
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License.
+ *
+ *
+ * 5.0.1 Jun 22 2001
+ * - Fixed bug in probe
+ * 5.0.0 Jun 06 2001
+ * - Driver was completely redesigned by Denis I.Timofeev,
+ * - now PCI/Dual, ISA/Dual (with single interrupt line) models are
+ * - supported
+ * 3.3.0 Thu Feb 24 21:30:28 NOVT 2000
+ * - PCI cards support
+ * 3.2.0 Mon Dec 13 22:26:53 NOVT 1999
+ * - Completely rebuilt all the packet storage system
+ * - to work in Ethernet-like style.
+ * 3.1.1 just fixed some bugs (5 aug 1999)
+ * 3.1.0 added balancing feature (26 apr 1999)
+ * 3.0.1 just fixed some bugs (14 apr 1999).
+ * 3.0.0 Initial Revision, Yaroslav Polyakov (24 Feb 1999)
+ * - added pre-calculation for CRC, fixed bug with "len-2" frames,
+ * - removed outbound fragmentation (MTU=1000), written CRC-calculation
+ * - on asm, added work with hard_headers and now we have our own cache
+ * - for them, optionally supported word-interchange on some chipsets,
+ *
+ * Known problem: this driver wasn't tested on multiprocessor machine.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ptrace.h>
+#include <linux/fcntl.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/pci.h>
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/types.h>
+#include <asm/byteorder.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#include "sbni.h"
+
+/* device private data */
+
+struct net_local {
+ struct net_device_stats stats;
+ struct timer_list watchdog;
+
+ spinlock_t lock;
+ struct sk_buff *rx_buf_p; /* receive buffer ptr */
+ struct sk_buff *tx_buf_p; /* transmit buffer ptr */
+
+ unsigned int framelen; /* current frame length */
+ unsigned int maxframe; /* maximum valid frame length */
+ unsigned int state;
+ unsigned int inppos, outpos; /* positions in rx/tx buffers */
+
+ /* transmitting frame number - from frames qty to 1 */
+ unsigned int tx_frameno;
+
+ /* expected number of next receiving frame */
+ unsigned int wait_frameno;
+
+ /* count of failed attempts to frame send - 32 attempts do before
+ error - while receiver tunes on opposite side of wire */
+ unsigned int trans_errors;
+
+ /* idle time; send pong when limit exceeded */
+ unsigned int timer_ticks;
+
+ /* fields used for receive level autoselection */
+ int delta_rxl;
+ unsigned int cur_rxl_index, timeout_rxl;
+ unsigned long cur_rxl_rcvd, prev_rxl_rcvd;
+
+ struct sbni_csr1 csr1; /* current value of CSR1 */
+ struct sbni_in_stats in_stats; /* internal statistics */
+
+ struct net_device *second; /* for ISA/dual cards */
+
+#ifdef CONFIG_SBNI_MULTILINE
+ struct net_device *master;
+ struct net_device *link;
+#endif
+};
+
+
+static int sbni_card_probe( unsigned long );
+static int sbni_pci_probe( struct net_device * );
+static struct net_device *sbni_probe1(struct net_device *, unsigned long, int);
+static int sbni_open( struct net_device * );
+static int sbni_close( struct net_device * );
+static int sbni_start_xmit( struct sk_buff *, struct net_device * );
+static int sbni_ioctl( struct net_device *, struct ifreq *, int );
+static struct net_device_stats *sbni_get_stats( struct net_device * );
+static void set_multicast_list( struct net_device * );
+
+static irqreturn_t sbni_interrupt( int, void *, struct pt_regs * );
+static void handle_channel( struct net_device * );
+static int recv_frame( struct net_device * );
+static void send_frame( struct net_device * );
+static int upload_data( struct net_device *,
+ unsigned, unsigned, unsigned, u32 );
+static void download_data( struct net_device *, u32 * );
+static void sbni_watchdog( unsigned long );
+static void interpret_ack( struct net_device *, unsigned );
+static int append_frame_to_pkt( struct net_device *, unsigned, u32 );
+static void indicate_pkt( struct net_device * );
+static void card_start( struct net_device * );
+static void prepare_to_send( struct sk_buff *, struct net_device * );
+static void drop_xmit_queue( struct net_device * );
+static void send_frame_header( struct net_device *, u32 * );
+static int skip_tail( unsigned int, unsigned int, u32 );
+static int check_fhdr( u32, u32 *, u32 *, u32 *, u32 *, u32 * );
+static void change_level( struct net_device * );
+static void timeout_change_level( struct net_device * );
+static u32 calc_crc32( u32, u8 *, u32 );
+static struct sk_buff * get_rx_buf( struct net_device * );
+static int sbni_init( struct net_device * );
+
+#ifdef CONFIG_SBNI_MULTILINE
+static int enslave( struct net_device *, struct net_device * );
+static int emancipate( struct net_device * );
+#endif
+
+#ifdef __i386__
+#define ASM_CRC 1
+#endif
+
+static const char version[] =
+ "Granch SBNI12 driver ver 5.0.1 Jun 22 2001 Denis I.Timofeev.\n";
+
+static int skip_pci_probe __initdata = 0;
+static int scandone __initdata = 0;
+static int num __initdata = 0;
+
+static unsigned char rxl_tab[];
+static u32 crc32tab[];
+
+/* A list of all installed devices, for removing the driver module. */
+static struct net_device *sbni_cards[ SBNI_MAX_NUM_CARDS ];
+
+/* Lists of device's parameters */
+static u32 io[ SBNI_MAX_NUM_CARDS ] __initdata =
+ { [0 ... SBNI_MAX_NUM_CARDS-1] = -1 };
+static u32 irq[ SBNI_MAX_NUM_CARDS ] __initdata;
+static u32 baud[ SBNI_MAX_NUM_CARDS ] __initdata;
+static u32 rxl[ SBNI_MAX_NUM_CARDS ] __initdata =
+ { [0 ... SBNI_MAX_NUM_CARDS-1] = -1 };
+static u32 mac[ SBNI_MAX_NUM_CARDS ] __initdata;
+
+#ifndef MODULE
+typedef u32 iarr[];
+static iarr __initdata *dest[5] = { &io, &irq, &baud, &rxl, &mac };
+#endif
+
+/* A zero-terminated list of I/O addresses to be probed on ISA bus */
+static unsigned int netcard_portlist[ ] __initdata = {
+ 0x210, 0x214, 0x220, 0x224, 0x230, 0x234, 0x240, 0x244, 0x250, 0x254,
+ 0x260, 0x264, 0x270, 0x274, 0x280, 0x284, 0x290, 0x294, 0x2a0, 0x2a4,
+ 0x2b0, 0x2b4, 0x2c0, 0x2c4, 0x2d0, 0x2d4, 0x2e0, 0x2e4, 0x2f0, 0x2f4,
+ 0 };
+
+
+/*
+ * Look for SBNI card which addr stored in dev->base_addr, if nonzero.
+ * Otherwise, look through PCI bus. If none PCI-card was found, scan ISA.
+ */
+
+static inline int __init
+sbni_isa_probe( struct net_device *dev )
+{
+ if( dev->base_addr > 0x1ff
+ && request_region( dev->base_addr, SBNI_IO_EXTENT, dev->name )
+ && sbni_probe1( dev, dev->base_addr, dev->irq ) )
+
+ return 0;
+ else {
+ printk( KERN_ERR "sbni: base address 0x%lx is busy, or adapter "
+ "is malfunctional!\n", dev->base_addr );
+ return -ENODEV;
+ }
+}
+
+static void __init sbni_devsetup(struct net_device *dev)
+{
+ ether_setup( dev );
+ dev->open = &sbni_open;
+ dev->stop = &sbni_close;
+ dev->hard_start_xmit = &sbni_start_xmit;
+ dev->get_stats = &sbni_get_stats;
+ dev->set_multicast_list = &set_multicast_list;
+ dev->do_ioctl = &sbni_ioctl;
+
+ SET_MODULE_OWNER( dev );
+}
+
+int __init sbni_probe(int unit)
+{
+ struct net_device *dev;
+ static unsigned version_printed __initdata = 0;
+ int err;
+
+ dev = alloc_netdev(sizeof(struct net_local), "sbni", sbni_devsetup);
+ if (!dev)
+ return -ENOMEM;
+
+ sprintf(dev->name, "sbni%d", unit);
+ netdev_boot_setup_check(dev);
+
+ err = sbni_init(dev);
+ if (err) {
+ free_netdev(dev);
+ return err;
+ }
+
+ err = register_netdev(dev);
+ if (err) {
+ release_region( dev->base_addr, SBNI_IO_EXTENT );
+ free_netdev(dev);
+ return err;
+ }
+ if( version_printed++ == 0 )
+ printk( KERN_INFO "%s", version );
+ return 0;
+}
+
+static int __init sbni_init(struct net_device *dev)
+{
+ int i;
+ if( dev->base_addr )
+ return sbni_isa_probe( dev );
+ /* otherwise we have to perform search our adapter */
+
+ if( io[ num ] != -1 )
+ dev->base_addr = io[ num ],
+ dev->irq = irq[ num ];
+ else if( scandone || io[ 0 ] != -1 )
+ return -ENODEV;
+
+ /* if io[ num ] contains non-zero address, then that is on ISA bus */
+ if( dev->base_addr )
+ return sbni_isa_probe( dev );
+
+ /* ...otherwise - scan PCI first */
+ if( !skip_pci_probe && !sbni_pci_probe( dev ) )
+ return 0;
+
+ if( io[ num ] == -1 ) {
+ /* Auto-scan will be stopped when first ISA card were found */
+ scandone = 1;
+ if( num > 0 )
+ return -ENODEV;
+ }
+
+ for( i = 0; netcard_portlist[ i ]; ++i ) {
+ int ioaddr = netcard_portlist[ i ];
+ if( request_region( ioaddr, SBNI_IO_EXTENT, dev->name )
+ && sbni_probe1( dev, ioaddr, 0 ))
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+
+int __init
+sbni_pci_probe( struct net_device *dev )
+{
+ struct pci_dev *pdev = NULL;
+
+ while( (pdev = pci_get_class( PCI_CLASS_NETWORK_OTHER << 8, pdev ))
+ != NULL ) {
+ int pci_irq_line;
+ unsigned long pci_ioaddr;
+ u16 subsys;
+
+ if( pdev->vendor != SBNI_PCI_VENDOR
+ && pdev->device != SBNI_PCI_DEVICE )
+ continue;
+
+ pci_ioaddr = pci_resource_start( pdev, 0 );
+ pci_irq_line = pdev->irq;
+
+ /* Avoid already found cards from previous calls */
+ if( !request_region( pci_ioaddr, SBNI_IO_EXTENT, dev->name ) ) {
+ pci_read_config_word( pdev, PCI_SUBSYSTEM_ID, &subsys );
+
+ if (subsys != 2)
+ continue;
+
+ /* Dual adapter is present */
+ if (!request_region(pci_ioaddr += 4, SBNI_IO_EXTENT,
+ dev->name ) )
+ continue;
+ }
+
+ if( pci_irq_line <= 0 || pci_irq_line >= NR_IRQS )
+ printk( KERN_WARNING " WARNING: The PCI BIOS assigned "
+ "this PCI card to IRQ %d, which is unlikely "
+ "to work!.\n"
+ KERN_WARNING " You should use the PCI BIOS "
+ "setup to assign a valid IRQ line.\n",
+ pci_irq_line );
+
+ /* avoiding re-enable dual adapters */
+ if( (pci_ioaddr & 7) == 0 && pci_enable_device( pdev ) ) {
+ release_region( pci_ioaddr, SBNI_IO_EXTENT );
+ pci_dev_put( pdev );
+ return -EIO;
+ }
+ if( sbni_probe1( dev, pci_ioaddr, pci_irq_line ) ) {
+ SET_NETDEV_DEV(dev, &pdev->dev);
+ /* not the best thing to do, but this is all messed up
+ for hotplug systems anyway... */
+ pci_dev_put( pdev );
+ return 0;
+ }
+ }
+ return -ENODEV;
+}
+
+
+static struct net_device * __init
+sbni_probe1( struct net_device *dev, unsigned long ioaddr, int irq )
+{
+ struct net_local *nl;
+
+ if( sbni_card_probe( ioaddr ) ) {
+ release_region( ioaddr, SBNI_IO_EXTENT );
+ return NULL;
+ }
+
+ outb( 0, ioaddr + CSR0 );
+
+ if( irq < 2 ) {
+ unsigned long irq_mask;
+
+ irq_mask = probe_irq_on();
+ outb( EN_INT | TR_REQ, ioaddr + CSR0 );
+ outb( PR_RES, ioaddr + CSR1 );
+ mdelay(50);
+ irq = probe_irq_off(irq_mask);
+ outb( 0, ioaddr + CSR0 );
+
+ if( !irq ) {
+ printk( KERN_ERR "%s: can't detect device irq!\n",
+ dev->name );
+ release_region( ioaddr, SBNI_IO_EXTENT );
+ return NULL;
+ }
+ } else if( irq == 2 )
+ irq = 9;
+
+ dev->irq = irq;
+ dev->base_addr = ioaddr;
+
+ /* Allocate dev->priv and fill in sbni-specific dev fields. */
+ nl = dev->priv;
+ if( !nl ) {
+ printk( KERN_ERR "%s: unable to get memory!\n", dev->name );
+ release_region( ioaddr, SBNI_IO_EXTENT );
+ return NULL;
+ }
+
+ dev->priv = nl;
+ memset( nl, 0, sizeof(struct net_local) );
+ spin_lock_init( &nl->lock );
+
+ /* store MAC address (generate if that isn't known) */
+ *(u16 *)dev->dev_addr = htons( 0x00ff );
+ *(u32 *)(dev->dev_addr + 2) = htonl( 0x01000000 |
+ ( (mac[num] ? mac[num] : (u32)((long)dev->priv)) & 0x00ffffff) );
+
+ /* store link settings (speed, receive level ) */
+ nl->maxframe = DEFAULT_FRAME_LEN;
+ nl->csr1.rate = baud[ num ];
+
+ if( (nl->cur_rxl_index = rxl[ num ]) == -1 )
+ /* autotune rxl */
+ nl->cur_rxl_index = DEF_RXL,
+ nl->delta_rxl = DEF_RXL_DELTA;
+ else
+ nl->delta_rxl = 0;
+ nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ];
+ if( inb( ioaddr + CSR0 ) & 0x01 )
+ nl->state |= FL_SLOW_MODE;
+
+ printk( KERN_NOTICE "%s: ioaddr %#lx, irq %d, "
+ "MAC: 00:ff:01:%02x:%02x:%02x\n",
+ dev->name, dev->base_addr, dev->irq,
+ ((u8 *) dev->dev_addr) [3],
+ ((u8 *) dev->dev_addr) [4],
+ ((u8 *) dev->dev_addr) [5] );
+
+ printk( KERN_NOTICE "%s: speed %d, receive level ", dev->name,
+ ( (nl->state & FL_SLOW_MODE) ? 500000 : 2000000)
+ / (1 << nl->csr1.rate) );
+
+ if( nl->delta_rxl == 0 )
+ printk( "0x%x (fixed)\n", nl->cur_rxl_index );
+ else
+ printk( "(auto)\n");
+
+#ifdef CONFIG_SBNI_MULTILINE
+ nl->master = dev;
+ nl->link = NULL;
+#endif
+
+ sbni_cards[ num++ ] = dev;
+ return dev;
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef CONFIG_SBNI_MULTILINE
+
+static int
+sbni_start_xmit( struct sk_buff *skb, struct net_device *dev )
+{
+ struct net_device *p;
+
+ netif_stop_queue( dev );
+
+ /* Looking for idle device in the list */
+ for( p = dev; p; ) {
+ struct net_local *nl = (struct net_local *) p->priv;
+ spin_lock( &nl->lock );
+ if( nl->tx_buf_p || (nl->state & FL_LINE_DOWN) ) {
+ p = nl->link;
+ spin_unlock( &nl->lock );
+ } else {
+ /* Idle dev is found */
+ prepare_to_send( skb, p );
+ spin_unlock( &nl->lock );
+ netif_start_queue( dev );
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+#else /* CONFIG_SBNI_MULTILINE */
+
+static int
+sbni_start_xmit( struct sk_buff *skb, struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ netif_stop_queue( dev );
+ spin_lock( &nl->lock );
+
+ prepare_to_send( skb, dev );
+
+ spin_unlock( &nl->lock );
+ return 0;
+}
+
+#endif /* CONFIG_SBNI_MULTILINE */
+
+/* -------------------------------------------------------------------------- */
+
+/* interrupt handler */
+
+/*
+ * SBNI12D-10, -11/ISA boards within "common interrupt" mode could not
+ * be looked as two independent single-channel devices. Every channel seems
+ * as Ethernet interface but interrupt handler must be common. Really, first
+ * channel ("master") driver only registers the handler. In its struct net_local
+ * it has got pointer to "slave" channel's struct net_local and handles that's
+ * interrupts too.
+ * dev of successfully attached ISA SBNI boards is linked to list.
+ * While next board driver is initialized, it scans this list. If one
+ * has found dev with same irq and ioaddr different by 4 then it assumes
+ * this board to be "master".
+ */
+
+static irqreturn_t
+sbni_interrupt( int irq, void *dev_id, struct pt_regs *regs )
+{
+ struct net_device *dev = (struct net_device *) dev_id;
+ struct net_local *nl = (struct net_local *) dev->priv;
+ int repeat;
+
+ spin_lock( &nl->lock );
+ if( nl->second )
+ spin_lock( &((struct net_local *) nl->second->priv)->lock );
+
+ do {
+ repeat = 0;
+ if( inb( dev->base_addr + CSR0 ) & (RC_RDY | TR_RDY) )
+ handle_channel( dev ),
+ repeat = 1;
+ if( nl->second && /* second channel present */
+ (inb( nl->second->base_addr+CSR0 ) & (RC_RDY | TR_RDY)) )
+ handle_channel( nl->second ),
+ repeat = 1;
+ } while( repeat );
+
+ if( nl->second )
+ spin_unlock( &((struct net_local *)nl->second->priv)->lock );
+ spin_unlock( &nl->lock );
+ return IRQ_HANDLED;
+}
+
+
+static void
+handle_channel( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ unsigned long ioaddr = dev->base_addr;
+
+ int req_ans;
+ unsigned char csr0;
+
+#ifdef CONFIG_SBNI_MULTILINE
+ /* Lock the master device because we going to change its local data */
+ if( nl->state & FL_SLAVE )
+ spin_lock( &((struct net_local *) nl->master->priv)->lock );
+#endif
+
+ outb( (inb( ioaddr + CSR0 ) & ~EN_INT) | TR_REQ, ioaddr + CSR0 );
+
+ nl->timer_ticks = CHANGE_LEVEL_START_TICKS;
+ for(;;) {
+ csr0 = inb( ioaddr + CSR0 );
+ if( ( csr0 & (RC_RDY | TR_RDY) ) == 0 )
+ break;
+
+ req_ans = !(nl->state & FL_PREV_OK);
+
+ if( csr0 & RC_RDY )
+ req_ans = recv_frame( dev );
+
+ /*
+ * TR_RDY always equals 1 here because we have owned the marker,
+ * and we set TR_REQ when disabled interrupts
+ */
+ csr0 = inb( ioaddr + CSR0 );
+ if( !(csr0 & TR_RDY) || (csr0 & RC_RDY) )
+ printk( KERN_ERR "%s: internal error!\n", dev->name );
+
+ /* if state & FL_NEED_RESEND != 0 then tx_frameno != 0 */
+ if( req_ans || nl->tx_frameno != 0 )
+ send_frame( dev );
+ else
+ /* send marker without any data */
+ outb( inb( ioaddr + CSR0 ) & ~TR_REQ, ioaddr + CSR0 );
+ }
+
+ outb( inb( ioaddr + CSR0 ) | EN_INT, ioaddr + CSR0 );
+
+#ifdef CONFIG_SBNI_MULTILINE
+ if( nl->state & FL_SLAVE )
+ spin_unlock( &((struct net_local *) nl->master->priv)->lock );
+#endif
+}
+
+
+/*
+ * Routine returns 1 if it need to acknoweledge received frame.
+ * Empty frame received without errors won't be acknoweledged.
+ */
+
+static int
+recv_frame( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ unsigned long ioaddr = dev->base_addr;
+
+ u32 crc = CRC32_INITIAL;
+
+ unsigned framelen, frameno, ack;
+ unsigned is_first, frame_ok;
+
+ if( check_fhdr( ioaddr, &framelen, &frameno, &ack, &is_first, &crc ) ) {
+ frame_ok = framelen > 4
+ ? upload_data( dev, framelen, frameno, is_first, crc )
+ : skip_tail( ioaddr, framelen, crc );
+ if( frame_ok )
+ interpret_ack( dev, ack );
+ } else
+ frame_ok = 0;
+
+ outb( inb( ioaddr + CSR0 ) ^ CT_ZER, ioaddr + CSR0 );
+ if( frame_ok ) {
+ nl->state |= FL_PREV_OK;
+ if( framelen > 4 )
+ nl->in_stats.all_rx_number++;
+ } else
+ nl->state &= ~FL_PREV_OK,
+ change_level( dev ),
+ nl->in_stats.all_rx_number++,
+ nl->in_stats.bad_rx_number++;
+
+ return !frame_ok || framelen > 4;
+}
+
+
+static void
+send_frame( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ u32 crc = CRC32_INITIAL;
+
+ if( nl->state & FL_NEED_RESEND ) {
+
+ /* if frame was sended but not ACK'ed - resend it */
+ if( nl->trans_errors ) {
+ --nl->trans_errors;
+ if( nl->framelen != 0 )
+ nl->in_stats.resend_tx_number++;
+ } else {
+ /* cannot xmit with many attempts */
+#ifdef CONFIG_SBNI_MULTILINE
+ if( (nl->state & FL_SLAVE) || nl->link )
+#endif
+ nl->state |= FL_LINE_DOWN;
+ drop_xmit_queue( dev );
+ goto do_send;
+ }
+ } else
+ nl->trans_errors = TR_ERROR_COUNT;
+
+ send_frame_header( dev, &crc );
+ nl->state |= FL_NEED_RESEND;
+ /*
+ * FL_NEED_RESEND will be cleared after ACK, but if empty
+ * frame sended then in prepare_to_send next frame
+ */
+
+
+ if( nl->framelen ) {
+ download_data( dev, &crc );
+ nl->in_stats.all_tx_number++;
+ nl->state |= FL_WAIT_ACK;
+ }
+
+ outsb( dev->base_addr + DAT, (u8 *)&crc, sizeof crc );
+
+do_send:
+ outb( inb( dev->base_addr + CSR0 ) & ~TR_REQ, dev->base_addr + CSR0 );
+
+ if( nl->tx_frameno )
+ /* next frame exists - we request card to send it */
+ outb( inb( dev->base_addr + CSR0 ) | TR_REQ,
+ dev->base_addr + CSR0 );
+}
+
+
+/*
+ * Write the frame data into adapter's buffer memory, and calculate CRC.
+ * Do padding if necessary.
+ */
+
+static void
+download_data( struct net_device *dev, u32 *crc_p )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ struct sk_buff *skb = nl->tx_buf_p;
+
+ unsigned len = min_t(unsigned int, skb->len - nl->outpos, nl->framelen);
+
+ outsb( dev->base_addr + DAT, skb->data + nl->outpos, len );
+ *crc_p = calc_crc32( *crc_p, skb->data + nl->outpos, len );
+
+ /* if packet too short we should write some more bytes to pad */
+ for( len = nl->framelen - len; len--; )
+ outb( 0, dev->base_addr + DAT ),
+ *crc_p = CRC32( 0, *crc_p );
+}
+
+
+static int
+upload_data( struct net_device *dev, unsigned framelen, unsigned frameno,
+ unsigned is_first, u32 crc )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ int frame_ok;
+
+ if( is_first )
+ nl->wait_frameno = frameno,
+ nl->inppos = 0;
+
+ if( nl->wait_frameno == frameno ) {
+
+ if( nl->inppos + framelen <= ETHER_MAX_LEN )
+ frame_ok = append_frame_to_pkt( dev, framelen, crc );
+
+ /*
+ * if CRC is right but framelen incorrect then transmitter
+ * error was occurred... drop entire packet
+ */
+ else if( (frame_ok = skip_tail( dev->base_addr, framelen, crc ))
+ != 0 )
+ nl->wait_frameno = 0,
+ nl->inppos = 0,
+#ifdef CONFIG_SBNI_MULTILINE
+ ((struct net_local *) nl->master->priv)
+ ->stats.rx_errors++,
+ ((struct net_local *) nl->master->priv)
+ ->stats.rx_missed_errors++;
+#else
+ nl->stats.rx_errors++,
+ nl->stats.rx_missed_errors++;
+#endif
+ /* now skip all frames until is_first != 0 */
+ } else
+ frame_ok = skip_tail( dev->base_addr, framelen, crc );
+
+ if( is_first && !frame_ok )
+ /*
+ * Frame has been broken, but we had already stored
+ * is_first... Drop entire packet.
+ */
+ nl->wait_frameno = 0,
+#ifdef CONFIG_SBNI_MULTILINE
+ ((struct net_local *) nl->master->priv)->stats.rx_errors++,
+ ((struct net_local *) nl->master->priv)->stats.rx_crc_errors++;
+#else
+ nl->stats.rx_errors++,
+ nl->stats.rx_crc_errors++;
+#endif
+
+ return frame_ok;
+}
+
+
+static __inline void
+send_complete( struct net_local *nl )
+{
+#ifdef CONFIG_SBNI_MULTILINE
+ ((struct net_local *) nl->master->priv)->stats.tx_packets++;
+ ((struct net_local *) nl->master->priv)->stats.tx_bytes
+ += nl->tx_buf_p->len;
+#else
+ nl->stats.tx_packets++;
+ nl->stats.tx_bytes += nl->tx_buf_p->len;
+#endif
+ dev_kfree_skb_irq( nl->tx_buf_p );
+
+ nl->tx_buf_p = NULL;
+
+ nl->outpos = 0;
+ nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+ nl->framelen = 0;
+}
+
+
+static void
+interpret_ack( struct net_device *dev, unsigned ack )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ if( ack == FRAME_SENT_OK ) {
+ nl->state &= ~FL_NEED_RESEND;
+
+ if( nl->state & FL_WAIT_ACK ) {
+ nl->outpos += nl->framelen;
+
+ if( --nl->tx_frameno )
+ nl->framelen = min_t(unsigned int,
+ nl->maxframe,
+ nl->tx_buf_p->len - nl->outpos);
+ else
+ send_complete( nl ),
+#ifdef CONFIG_SBNI_MULTILINE
+ netif_wake_queue( nl->master );
+#else
+ netif_wake_queue( dev );
+#endif
+ }
+ }
+
+ nl->state &= ~FL_WAIT_ACK;
+}
+
+
+/*
+ * Glue received frame with previous fragments of packet.
+ * Indicate packet when last frame would be accepted.
+ */
+
+static int
+append_frame_to_pkt( struct net_device *dev, unsigned framelen, u32 crc )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ u8 *p;
+
+ if( nl->inppos + framelen > ETHER_MAX_LEN )
+ return 0;
+
+ if( !nl->rx_buf_p && !(nl->rx_buf_p = get_rx_buf( dev )) )
+ return 0;
+
+ p = nl->rx_buf_p->data + nl->inppos;
+ insb( dev->base_addr + DAT, p, framelen );
+ if( calc_crc32( crc, p, framelen ) != CRC32_REMAINDER )
+ return 0;
+
+ nl->inppos += framelen - 4;
+ if( --nl->wait_frameno == 0 ) /* last frame received */
+ indicate_pkt( dev );
+
+ return 1;
+}
+
+
+/*
+ * Prepare to start output on adapter.
+ * Transmitter will be actually activated when marker is accepted.
+ */
+
+static void
+prepare_to_send( struct sk_buff *skb, struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ unsigned int len;
+
+ /* nl->tx_buf_p == NULL here! */
+ if( nl->tx_buf_p )
+ printk( KERN_ERR "%s: memory leak!\n", dev->name );
+
+ nl->outpos = 0;
+ nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+
+ len = skb->len;
+ if( len < SBNI_MIN_LEN )
+ len = SBNI_MIN_LEN;
+
+ nl->tx_buf_p = skb;
+ nl->tx_frameno = (len + nl->maxframe - 1) / nl->maxframe;
+ nl->framelen = len < nl->maxframe ? len : nl->maxframe;
+
+ outb( inb( dev->base_addr + CSR0 ) | TR_REQ, dev->base_addr + CSR0 );
+#ifdef CONFIG_SBNI_MULTILINE
+ nl->master->trans_start = jiffies;
+#else
+ dev->trans_start = jiffies;
+#endif
+}
+
+
+static void
+drop_xmit_queue( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ if( nl->tx_buf_p )
+ dev_kfree_skb_any( nl->tx_buf_p ),
+ nl->tx_buf_p = NULL,
+#ifdef CONFIG_SBNI_MULTILINE
+ ((struct net_local *) nl->master->priv)
+ ->stats.tx_errors++,
+ ((struct net_local *) nl->master->priv)
+ ->stats.tx_carrier_errors++;
+#else
+ nl->stats.tx_errors++,
+ nl->stats.tx_carrier_errors++;
+#endif
+
+ nl->tx_frameno = 0;
+ nl->framelen = 0;
+ nl->outpos = 0;
+ nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+#ifdef CONFIG_SBNI_MULTILINE
+ netif_start_queue( nl->master );
+ nl->master->trans_start = jiffies;
+#else
+ netif_start_queue( dev );
+ dev->trans_start = jiffies;
+#endif
+}
+
+
+static void
+send_frame_header( struct net_device *dev, u32 *crc_p )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ u32 crc = *crc_p;
+ u32 len_field = nl->framelen + 6; /* CRC + frameno + reserved */
+ u8 value;
+
+ if( nl->state & FL_NEED_RESEND )
+ len_field |= FRAME_RETRY; /* non-first attempt... */
+
+ if( nl->outpos == 0 )
+ len_field |= FRAME_FIRST;
+
+ len_field |= (nl->state & FL_PREV_OK) ? FRAME_SENT_OK : FRAME_SENT_BAD;
+ outb( SBNI_SIG, dev->base_addr + DAT );
+
+ value = (u8) len_field;
+ outb( value, dev->base_addr + DAT );
+ crc = CRC32( value, crc );
+ value = (u8) (len_field >> 8);
+ outb( value, dev->base_addr + DAT );
+ crc = CRC32( value, crc );
+
+ outb( nl->tx_frameno, dev->base_addr + DAT );
+ crc = CRC32( nl->tx_frameno, crc );
+ outb( 0, dev->base_addr + DAT );
+ crc = CRC32( 0, crc );
+ *crc_p = crc;
+}
+
+
+/*
+ * if frame tail not needed (incorrect number or received twice),
+ * it won't store, but CRC will be calculated
+ */
+
+static int
+skip_tail( unsigned int ioaddr, unsigned int tail_len, u32 crc )
+{
+ while( tail_len-- )
+ crc = CRC32( inb( ioaddr + DAT ), crc );
+
+ return crc == CRC32_REMAINDER;
+}
+
+
+/*
+ * Preliminary checks if frame header is correct, calculates its CRC
+ * and split it to simple fields
+ */
+
+static int
+check_fhdr( u32 ioaddr, u32 *framelen, u32 *frameno, u32 *ack,
+ u32 *is_first, u32 *crc_p )
+{
+ u32 crc = *crc_p;
+ u8 value;
+
+ if( inb( ioaddr + DAT ) != SBNI_SIG )
+ return 0;
+
+ value = inb( ioaddr + DAT );
+ *framelen = (u32)value;
+ crc = CRC32( value, crc );
+ value = inb( ioaddr + DAT );
+ *framelen |= ((u32)value) << 8;
+ crc = CRC32( value, crc );
+
+ *ack = *framelen & FRAME_ACK_MASK;
+ *is_first = (*framelen & FRAME_FIRST) != 0;
+
+ if( (*framelen &= FRAME_LEN_MASK) < 6
+ || *framelen > SBNI_MAX_FRAME - 3 )
+ return 0;
+
+ value = inb( ioaddr + DAT );
+ *frameno = (u32)value;
+ crc = CRC32( value, crc );
+
+ crc = CRC32( inb( ioaddr + DAT ), crc ); /* reserved byte */
+ *framelen -= 2;
+
+ *crc_p = crc;
+ return 1;
+}
+
+
+static struct sk_buff *
+get_rx_buf( struct net_device *dev )
+{
+ /* +2 is to compensate for the alignment fixup below */
+ struct sk_buff *skb = dev_alloc_skb( ETHER_MAX_LEN + 2 );
+ if( !skb )
+ return NULL;
+
+#ifdef CONFIG_SBNI_MULTILINE
+ skb->dev = ((struct net_local *) dev->priv)->master;
+#else
+ skb->dev = dev;
+#endif
+ skb_reserve( skb, 2 ); /* Align IP on longword boundaries */
+ return skb;
+}
+
+
+static void
+indicate_pkt( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ struct sk_buff *skb = nl->rx_buf_p;
+
+ skb_put( skb, nl->inppos );
+
+#ifdef CONFIG_SBNI_MULTILINE
+ skb->protocol = eth_type_trans( skb, nl->master );
+ netif_rx( skb );
+ dev->last_rx = jiffies;
+ ++((struct net_local *) nl->master->priv)->stats.rx_packets;
+ ((struct net_local *) nl->master->priv)->stats.rx_bytes += nl->inppos;
+#else
+ skb->protocol = eth_type_trans( skb, dev );
+ netif_rx( skb );
+ dev->last_rx = jiffies;
+ ++nl->stats.rx_packets;
+ nl->stats.rx_bytes += nl->inppos;
+#endif
+ nl->rx_buf_p = NULL; /* protocol driver will clear this sk_buff */
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Routine checks periodically wire activity and regenerates marker if
+ * connect was inactive for a long time.
+ */
+
+static void
+sbni_watchdog( unsigned long arg )
+{
+ struct net_device *dev = (struct net_device *) arg;
+ struct net_local *nl = (struct net_local *) dev->priv;
+ struct timer_list *w = &nl->watchdog;
+ unsigned long flags;
+ unsigned char csr0;
+
+ spin_lock_irqsave( &nl->lock, flags );
+
+ csr0 = inb( dev->base_addr + CSR0 );
+ if( csr0 & RC_CHK ) {
+
+ if( nl->timer_ticks ) {
+ if( csr0 & (RC_RDY | BU_EMP) )
+ /* receiving not active */
+ nl->timer_ticks--;
+ } else {
+ nl->in_stats.timeout_number++;
+ if( nl->delta_rxl )
+ timeout_change_level( dev );
+
+ outb( *(u_char *)&nl->csr1 | PR_RES,
+ dev->base_addr + CSR1 );
+ csr0 = inb( dev->base_addr + CSR0 );
+ }
+ } else
+ nl->state &= ~FL_LINE_DOWN;
+
+ outb( csr0 | RC_CHK, dev->base_addr + CSR0 );
+
+ init_timer( w );
+ w->expires = jiffies + SBNI_TIMEOUT;
+ w->data = arg;
+ w->function = sbni_watchdog;
+ add_timer( w );
+
+ spin_unlock_irqrestore( &nl->lock, flags );
+}
+
+
+static unsigned char rxl_tab[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
+ 0x0a, 0x0c, 0x0f, 0x16, 0x18, 0x1a, 0x1c, 0x1f
+};
+
+#define SIZE_OF_TIMEOUT_RXL_TAB 4
+static unsigned char timeout_rxl_tab[] = {
+ 0x03, 0x05, 0x08, 0x0b
+};
+
+/* -------------------------------------------------------------------------- */
+
+static void
+card_start( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ nl->timer_ticks = CHANGE_LEVEL_START_TICKS;
+ nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND);
+ nl->state |= FL_PREV_OK;
+
+ nl->inppos = nl->outpos = 0;
+ nl->wait_frameno = 0;
+ nl->tx_frameno = 0;
+ nl->framelen = 0;
+
+ outb( *(u_char *)&nl->csr1 | PR_RES, dev->base_addr + CSR1 );
+ outb( EN_INT, dev->base_addr + CSR0 );
+}
+
+/* -------------------------------------------------------------------------- */
+
+/* Receive level auto-selection */
+
+static void
+change_level( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ if( nl->delta_rxl == 0 ) /* do not auto-negotiate RxL */
+ return;
+
+ if( nl->cur_rxl_index == 0 )
+ nl->delta_rxl = 1;
+ else if( nl->cur_rxl_index == 15 )
+ nl->delta_rxl = -1;
+ else if( nl->cur_rxl_rcvd < nl->prev_rxl_rcvd )
+ nl->delta_rxl = -nl->delta_rxl;
+
+ nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index += nl->delta_rxl ];
+ inb( dev->base_addr + CSR0 ); /* needs for PCI cards */
+ outb( *(u8 *)&nl->csr1, dev->base_addr + CSR1 );
+
+ nl->prev_rxl_rcvd = nl->cur_rxl_rcvd;
+ nl->cur_rxl_rcvd = 0;
+}
+
+
+static void
+timeout_change_level( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ nl->cur_rxl_index = timeout_rxl_tab[ nl->timeout_rxl ];
+ if( ++nl->timeout_rxl >= 4 )
+ nl->timeout_rxl = 0;
+
+ nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ];
+ inb( dev->base_addr + CSR0 );
+ outb( *(unsigned char *)&nl->csr1, dev->base_addr + CSR1 );
+
+ nl->prev_rxl_rcvd = nl->cur_rxl_rcvd;
+ nl->cur_rxl_rcvd = 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Open/initialize the board.
+ */
+
+static int
+sbni_open( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ struct timer_list *w = &nl->watchdog;
+
+ /*
+ * For double ISA adapters within "common irq" mode, we have to
+ * determine whether primary or secondary channel is initialized,
+ * and set the irq handler only in first case.
+ */
+ if( dev->base_addr < 0x400 ) { /* ISA only */
+ struct net_device **p = sbni_cards;
+ for( ; *p && p < sbni_cards + SBNI_MAX_NUM_CARDS; ++p )
+ if( (*p)->irq == dev->irq
+ && ((*p)->base_addr == dev->base_addr + 4
+ || (*p)->base_addr == dev->base_addr - 4)
+ && (*p)->flags & IFF_UP ) {
+
+ ((struct net_local *) ((*p)->priv))
+ ->second = dev;
+ printk( KERN_NOTICE "%s: using shared irq "
+ "with %s\n", dev->name, (*p)->name );
+ nl->state |= FL_SECONDARY;
+ goto handler_attached;
+ }
+ }
+
+ if( request_irq(dev->irq, sbni_interrupt, SA_SHIRQ, dev->name, dev) ) {
+ printk( KERN_ERR "%s: unable to get IRQ %d.\n",
+ dev->name, dev->irq );
+ return -EAGAIN;
+ }
+
+handler_attached:
+
+ spin_lock( &nl->lock );
+ memset( &nl->stats, 0, sizeof(struct net_device_stats) );
+ memset( &nl->in_stats, 0, sizeof(struct sbni_in_stats) );
+
+ card_start( dev );
+
+ netif_start_queue( dev );
+
+ /* set timer watchdog */
+ init_timer( w );
+ w->expires = jiffies + SBNI_TIMEOUT;
+ w->data = (unsigned long) dev;
+ w->function = sbni_watchdog;
+ add_timer( w );
+
+ spin_unlock( &nl->lock );
+ return 0;
+}
+
+
+static int
+sbni_close( struct net_device *dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+
+ if( nl->second && nl->second->flags & IFF_UP ) {
+ printk( KERN_NOTICE "Secondary channel (%s) is active!\n",
+ nl->second->name );
+ return -EBUSY;
+ }
+
+#ifdef CONFIG_SBNI_MULTILINE
+ if( nl->state & FL_SLAVE )
+ emancipate( dev );
+ else
+ while( nl->link ) /* it's master device! */
+ emancipate( nl->link );
+#endif
+
+ spin_lock( &nl->lock );
+
+ nl->second = NULL;
+ drop_xmit_queue( dev );
+ netif_stop_queue( dev );
+
+ del_timer( &nl->watchdog );
+
+ outb( 0, dev->base_addr + CSR0 );
+
+ if( !(nl->state & FL_SECONDARY) )
+ free_irq( dev->irq, dev );
+ nl->state &= FL_SECONDARY;
+
+ spin_unlock( &nl->lock );
+ return 0;
+}
+
+
+/*
+ Valid combinations in CSR0 (for probing):
+
+ VALID_DECODER 0000,0011,1011,1010
+
+ ; 0 ; -
+ TR_REQ ; 1 ; +
+ TR_RDY ; 2 ; -
+ TR_RDY TR_REQ ; 3 ; +
+ BU_EMP ; 4 ; +
+ BU_EMP TR_REQ ; 5 ; +
+ BU_EMP TR_RDY ; 6 ; -
+ BU_EMP TR_RDY TR_REQ ; 7 ; +
+ RC_RDY ; 8 ; +
+ RC_RDY TR_REQ ; 9 ; +
+ RC_RDY TR_RDY ; 10 ; -
+ RC_RDY TR_RDY TR_REQ ; 11 ; -
+ RC_RDY BU_EMP ; 12 ; -
+ RC_RDY BU_EMP TR_REQ ; 13 ; -
+ RC_RDY BU_EMP TR_RDY ; 14 ; -
+ RC_RDY BU_EMP TR_RDY TR_REQ ; 15 ; -
+*/
+
+#define VALID_DECODER (2 + 8 + 0x10 + 0x20 + 0x80 + 0x100 + 0x200)
+
+
+static int
+sbni_card_probe( unsigned long ioaddr )
+{
+ unsigned char csr0;
+
+ csr0 = inb( ioaddr + CSR0 );
+ if( csr0 != 0xff && csr0 != 0x00 ) {
+ csr0 &= ~EN_INT;
+ if( csr0 & BU_EMP )
+ csr0 |= EN_INT;
+
+ if( VALID_DECODER & (1 << (csr0 >> 4)) )
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int
+sbni_ioctl( struct net_device *dev, struct ifreq *ifr, int cmd )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ struct sbni_flags flags;
+ int error = 0;
+
+#ifdef CONFIG_SBNI_MULTILINE
+ struct net_device *slave_dev;
+ char slave_name[ 8 ];
+#endif
+
+ switch( cmd ) {
+ case SIOCDEVGETINSTATS :
+ if (copy_to_user( ifr->ifr_data, &nl->in_stats,
+ sizeof(struct sbni_in_stats) ))
+ error = -EFAULT;
+ break;
+
+ case SIOCDEVRESINSTATS :
+ if( current->euid != 0 ) /* root only */
+ return -EPERM;
+ memset( &nl->in_stats, 0, sizeof(struct sbni_in_stats) );
+ break;
+
+ case SIOCDEVGHWSTATE :
+ flags.mac_addr = *(u32 *)(dev->dev_addr + 3);
+ flags.rate = nl->csr1.rate;
+ flags.slow_mode = (nl->state & FL_SLOW_MODE) != 0;
+ flags.rxl = nl->cur_rxl_index;
+ flags.fixed_rxl = nl->delta_rxl == 0;
+
+ if (copy_to_user( ifr->ifr_data, &flags, sizeof flags ))
+ error = -EFAULT;
+ break;
+
+ case SIOCDEVSHWSTATE :
+ if( current->euid != 0 ) /* root only */
+ return -EPERM;
+
+ spin_lock( &nl->lock );
+ flags = *(struct sbni_flags*) &ifr->ifr_ifru;
+ if( flags.fixed_rxl )
+ nl->delta_rxl = 0,
+ nl->cur_rxl_index = flags.rxl;
+ else
+ nl->delta_rxl = DEF_RXL_DELTA,
+ nl->cur_rxl_index = DEF_RXL;
+
+ nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ];
+ nl->csr1.rate = flags.rate;
+ outb( *(u8 *)&nl->csr1 | PR_RES, dev->base_addr + CSR1 );
+ spin_unlock( &nl->lock );
+ break;
+
+#ifdef CONFIG_SBNI_MULTILINE
+
+ case SIOCDEVENSLAVE :
+ if( current->euid != 0 ) /* root only */
+ return -EPERM;
+
+ if (copy_from_user( slave_name, ifr->ifr_data, sizeof slave_name ))
+ return -EFAULT;
+ slave_dev = dev_get_by_name( slave_name );
+ if( !slave_dev || !(slave_dev->flags & IFF_UP) ) {
+ printk( KERN_ERR "%s: trying to enslave non-active "
+ "device %s\n", dev->name, slave_name );
+ return -EPERM;
+ }
+
+ return enslave( dev, slave_dev );
+
+ case SIOCDEVEMANSIPATE :
+ if( current->euid != 0 ) /* root only */
+ return -EPERM;
+
+ return emancipate( dev );
+
+#endif /* CONFIG_SBNI_MULTILINE */
+
+ default :
+ return -EOPNOTSUPP;
+ }
+
+ return error;
+}
+
+
+#ifdef CONFIG_SBNI_MULTILINE
+
+static int
+enslave( struct net_device *dev, struct net_device *slave_dev )
+{
+ struct net_local *nl = (struct net_local *) dev->priv;
+ struct net_local *snl = (struct net_local *) slave_dev->priv;
+
+ if( nl->state & FL_SLAVE ) /* This isn't master or free device */
+ return -EBUSY;
+
+ if( snl->state & FL_SLAVE ) /* That was already enslaved */
+ return -EBUSY;
+
+ spin_lock( &nl->lock );
+ spin_lock( &snl->lock );
+
+ /* append to list */
+ snl->link = nl->link;
+ nl->link = slave_dev;
+ snl->master = dev;
+ snl->state |= FL_SLAVE;
+
+ /* Summary statistics of MultiLine operation will be stored
+ in master's counters */
+ memset( &snl->stats, 0, sizeof(struct net_device_stats) );
+ netif_stop_queue( slave_dev );
+ netif_wake_queue( dev ); /* Now we are able to transmit */
+
+ spin_unlock( &snl->lock );
+ spin_unlock( &nl->lock );
+ printk( KERN_NOTICE "%s: slave device (%s) attached.\n",
+ dev->name, slave_dev->name );
+ return 0;
+}
+
+
+static int
+emancipate( struct net_device *dev )
+{
+ struct net_local *snl = (struct net_local *) dev->priv;
+ struct net_device *p = snl->master;
+ struct net_local *nl = (struct net_local *) p->priv;
+
+ if( !(snl->state & FL_SLAVE) )
+ return -EINVAL;
+
+ spin_lock( &nl->lock );
+ spin_lock( &snl->lock );
+ drop_xmit_queue( dev );
+
+ /* exclude from list */
+ for(;;) { /* must be in list */
+ struct net_local *t = (struct net_local *) p->priv;
+ if( t->link == dev ) {
+ t->link = snl->link;
+ break;
+ }
+ p = t->link;
+ }
+
+ snl->link = NULL;
+ snl->master = dev;
+ snl->state &= ~FL_SLAVE;
+
+ netif_start_queue( dev );
+
+ spin_unlock( &snl->lock );
+ spin_unlock( &nl->lock );
+
+ dev_put( dev );
+ return 0;
+}
+
+#endif
+
+
+static struct net_device_stats *
+sbni_get_stats( struct net_device *dev )
+{
+ return &((struct net_local *) dev->priv)->stats;
+}
+
+
+static void
+set_multicast_list( struct net_device *dev )
+{
+ return; /* sbni always operate in promiscuos mode */
+}
+
+
+#ifdef MODULE
+module_param_array(io, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+module_param_array(baud, int, NULL, 0);
+module_param_array(rxl, int, NULL, 0);
+module_param_array(mac, int, NULL, 0);
+module_param(skip_pci_probe, bool, 0);
+
+MODULE_LICENSE("GPL");
+
+
+int
+init_module( void )
+{
+ struct net_device *dev;
+ int err;
+
+ while( num < SBNI_MAX_NUM_CARDS ) {
+ dev = alloc_netdev(sizeof(struct net_local),
+ "sbni%d", sbni_devsetup);
+ if( !dev)
+ break;
+
+ sprintf( dev->name, "sbni%d", num );
+
+ err = sbni_init(dev);
+ if (err) {
+ free_netdev(dev);
+ break;
+ }
+
+ if( register_netdev( dev ) ) {
+ release_region( dev->base_addr, SBNI_IO_EXTENT );
+ free_netdev( dev );
+ break;
+ }
+ }
+
+ return *sbni_cards ? 0 : -ENODEV;
+}
+
+void
+cleanup_module( void )
+{
+ struct net_device *dev;
+ int num;
+
+ for( num = 0; num < SBNI_MAX_NUM_CARDS; ++num )
+ if( (dev = sbni_cards[ num ]) != NULL ) {
+ unregister_netdev( dev );
+ release_region( dev->base_addr, SBNI_IO_EXTENT );
+ free_netdev( dev );
+ }
+}
+
+#else /* MODULE */
+
+static int __init
+sbni_setup( char *p )
+{
+ int n, parm;
+
+ if( *p++ != '(' )
+ goto bad_param;
+
+ for( n = 0, parm = 0; *p && n < 8; ) {
+ (*dest[ parm ])[ n ] = simple_strtol( p, &p, 0 );
+ if( !*p || *p == ')' )
+ return 1;
+ if( *p == ';' )
+ ++p, ++n, parm = 0;
+ else if( *p++ != ',' )
+ break;
+ else
+ if( ++parm >= 5 )
+ break;
+ }
+bad_param:
+ printk( KERN_ERR "Error in sbni kernel parameter!\n" );
+ return 0;
+}
+
+__setup( "sbni=", sbni_setup );
+
+#endif /* MODULE */
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef ASM_CRC
+
+static u32
+calc_crc32( u32 crc, u8 *p, u32 len )
+{
+ register u32 _crc;
+ _crc = crc;
+
+ __asm__ __volatile__ (
+ "xorl %%ebx, %%ebx\n"
+ "movl %2, %%esi\n"
+ "movl %3, %%ecx\n"
+ "movl $crc32tab, %%edi\n"
+ "shrl $2, %%ecx\n"
+ "jz 1f\n"
+
+ ".align 4\n"
+ "0:\n"
+ "movb %%al, %%bl\n"
+ "movl (%%esi), %%edx\n"
+ "shrl $8, %%eax\n"
+ "xorb %%dl, %%bl\n"
+ "shrl $8, %%edx\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+
+ "movb %%al, %%bl\n"
+ "shrl $8, %%eax\n"
+ "xorb %%dl, %%bl\n"
+ "shrl $8, %%edx\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+
+ "movb %%al, %%bl\n"
+ "shrl $8, %%eax\n"
+ "xorb %%dl, %%bl\n"
+ "movb %%dh, %%dl\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+
+ "movb %%al, %%bl\n"
+ "shrl $8, %%eax\n"
+ "xorb %%dl, %%bl\n"
+ "addl $4, %%esi\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+
+ "decl %%ecx\n"
+ "jnz 0b\n"
+
+ "1:\n"
+ "movl %3, %%ecx\n"
+ "andl $3, %%ecx\n"
+ "jz 2f\n"
+
+ "movb %%al, %%bl\n"
+ "shrl $8, %%eax\n"
+ "xorb (%%esi), %%bl\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+
+ "decl %%ecx\n"
+ "jz 2f\n"
+
+ "movb %%al, %%bl\n"
+ "shrl $8, %%eax\n"
+ "xorb 1(%%esi), %%bl\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+
+ "decl %%ecx\n"
+ "jz 2f\n"
+
+ "movb %%al, %%bl\n"
+ "shrl $8, %%eax\n"
+ "xorb 2(%%esi), %%bl\n"
+ "xorl (%%edi,%%ebx,4), %%eax\n"
+ "2:\n"
+ : "=a" (_crc)
+ : "0" (_crc), "g" (p), "g" (len)
+ : "bx", "cx", "dx", "si", "di"
+ );
+
+ return _crc;
+}
+
+#else /* ASM_CRC */
+
+static u32
+calc_crc32( u32 crc, u8 *p, u32 len )
+{
+ while( len-- )
+ crc = CRC32( *p++, crc );
+
+ return crc;
+}
+
+#endif /* ASM_CRC */
+
+
+static u32 crc32tab[] __attribute__ ((aligned(8))) = {
+ 0xD202EF8D, 0xA505DF1B, 0x3C0C8EA1, 0x4B0BBE37,
+ 0xD56F2B94, 0xA2681B02, 0x3B614AB8, 0x4C667A2E,
+ 0xDCD967BF, 0xABDE5729, 0x32D70693, 0x45D03605,
+ 0xDBB4A3A6, 0xACB39330, 0x35BAC28A, 0x42BDF21C,
+ 0xCFB5FFE9, 0xB8B2CF7F, 0x21BB9EC5, 0x56BCAE53,
+ 0xC8D83BF0, 0xBFDF0B66, 0x26D65ADC, 0x51D16A4A,
+ 0xC16E77DB, 0xB669474D, 0x2F6016F7, 0x58672661,
+ 0xC603B3C2, 0xB1048354, 0x280DD2EE, 0x5F0AE278,
+ 0xE96CCF45, 0x9E6BFFD3, 0x0762AE69, 0x70659EFF,
+ 0xEE010B5C, 0x99063BCA, 0x000F6A70, 0x77085AE6,
+ 0xE7B74777, 0x90B077E1, 0x09B9265B, 0x7EBE16CD,
+ 0xE0DA836E, 0x97DDB3F8, 0x0ED4E242, 0x79D3D2D4,
+ 0xF4DBDF21, 0x83DCEFB7, 0x1AD5BE0D, 0x6DD28E9B,
+ 0xF3B61B38, 0x84B12BAE, 0x1DB87A14, 0x6ABF4A82,
+ 0xFA005713, 0x8D076785, 0x140E363F, 0x630906A9,
+ 0xFD6D930A, 0x8A6AA39C, 0x1363F226, 0x6464C2B0,
+ 0xA4DEAE1D, 0xD3D99E8B, 0x4AD0CF31, 0x3DD7FFA7,
+ 0xA3B36A04, 0xD4B45A92, 0x4DBD0B28, 0x3ABA3BBE,
+ 0xAA05262F, 0xDD0216B9, 0x440B4703, 0x330C7795,
+ 0xAD68E236, 0xDA6FD2A0, 0x4366831A, 0x3461B38C,
+ 0xB969BE79, 0xCE6E8EEF, 0x5767DF55, 0x2060EFC3,
+ 0xBE047A60, 0xC9034AF6, 0x500A1B4C, 0x270D2BDA,
+ 0xB7B2364B, 0xC0B506DD, 0x59BC5767, 0x2EBB67F1,
+ 0xB0DFF252, 0xC7D8C2C4, 0x5ED1937E, 0x29D6A3E8,
+ 0x9FB08ED5, 0xE8B7BE43, 0x71BEEFF9, 0x06B9DF6F,
+ 0x98DD4ACC, 0xEFDA7A5A, 0x76D32BE0, 0x01D41B76,
+ 0x916B06E7, 0xE66C3671, 0x7F6567CB, 0x0862575D,
+ 0x9606C2FE, 0xE101F268, 0x7808A3D2, 0x0F0F9344,
+ 0x82079EB1, 0xF500AE27, 0x6C09FF9D, 0x1B0ECF0B,
+ 0x856A5AA8, 0xF26D6A3E, 0x6B643B84, 0x1C630B12,
+ 0x8CDC1683, 0xFBDB2615, 0x62D277AF, 0x15D54739,
+ 0x8BB1D29A, 0xFCB6E20C, 0x65BFB3B6, 0x12B88320,
+ 0x3FBA6CAD, 0x48BD5C3B, 0xD1B40D81, 0xA6B33D17,
+ 0x38D7A8B4, 0x4FD09822, 0xD6D9C998, 0xA1DEF90E,
+ 0x3161E49F, 0x4666D409, 0xDF6F85B3, 0xA868B525,
+ 0x360C2086, 0x410B1010, 0xD80241AA, 0xAF05713C,
+ 0x220D7CC9, 0x550A4C5F, 0xCC031DE5, 0xBB042D73,
+ 0x2560B8D0, 0x52678846, 0xCB6ED9FC, 0xBC69E96A,
+ 0x2CD6F4FB, 0x5BD1C46D, 0xC2D895D7, 0xB5DFA541,
+ 0x2BBB30E2, 0x5CBC0074, 0xC5B551CE, 0xB2B26158,
+ 0x04D44C65, 0x73D37CF3, 0xEADA2D49, 0x9DDD1DDF,
+ 0x03B9887C, 0x74BEB8EA, 0xEDB7E950, 0x9AB0D9C6,
+ 0x0A0FC457, 0x7D08F4C1, 0xE401A57B, 0x930695ED,
+ 0x0D62004E, 0x7A6530D8, 0xE36C6162, 0x946B51F4,
+ 0x19635C01, 0x6E646C97, 0xF76D3D2D, 0x806A0DBB,
+ 0x1E0E9818, 0x6909A88E, 0xF000F934, 0x8707C9A2,
+ 0x17B8D433, 0x60BFE4A5, 0xF9B6B51F, 0x8EB18589,
+ 0x10D5102A, 0x67D220BC, 0xFEDB7106, 0x89DC4190,
+ 0x49662D3D, 0x3E611DAB, 0xA7684C11, 0xD06F7C87,
+ 0x4E0BE924, 0x390CD9B2, 0xA0058808, 0xD702B89E,
+ 0x47BDA50F, 0x30BA9599, 0xA9B3C423, 0xDEB4F4B5,
+ 0x40D06116, 0x37D75180, 0xAEDE003A, 0xD9D930AC,
+ 0x54D13D59, 0x23D60DCF, 0xBADF5C75, 0xCDD86CE3,
+ 0x53BCF940, 0x24BBC9D6, 0xBDB2986C, 0xCAB5A8FA,
+ 0x5A0AB56B, 0x2D0D85FD, 0xB404D447, 0xC303E4D1,
+ 0x5D677172, 0x2A6041E4, 0xB369105E, 0xC46E20C8,
+ 0x72080DF5, 0x050F3D63, 0x9C066CD9, 0xEB015C4F,
+ 0x7565C9EC, 0x0262F97A, 0x9B6BA8C0, 0xEC6C9856,
+ 0x7CD385C7, 0x0BD4B551, 0x92DDE4EB, 0xE5DAD47D,
+ 0x7BBE41DE, 0x0CB97148, 0x95B020F2, 0xE2B71064,
+ 0x6FBF1D91, 0x18B82D07, 0x81B17CBD, 0xF6B64C2B,
+ 0x68D2D988, 0x1FD5E91E, 0x86DCB8A4, 0xF1DB8832,
+ 0x616495A3, 0x1663A535, 0x8F6AF48F, 0xF86DC419,
+ 0x660951BA, 0x110E612C, 0x88073096, 0xFF000000
+};
+
diff --git a/drivers/net/wan/sbni.h b/drivers/net/wan/sbni.h
new file mode 100644
index 00000000000..27715e70f28
--- /dev/null
+++ b/drivers/net/wan/sbni.h
@@ -0,0 +1,141 @@
+/* sbni.h: definitions for a Granch SBNI12 driver, version 5.0.0
+ * Written 2001 Denis I.Timofeev (timofeev@granch.ru)
+ * This file is distributed under the GNU GPL
+ */
+
+#ifndef SBNI_H
+#define SBNI_H
+
+#ifdef SBNI_DEBUG
+#define DP( A ) A
+#else
+#define DP( A )
+#endif
+
+
+/* We don't have official vendor id yet... */
+#define SBNI_PCI_VENDOR 0x55
+#define SBNI_PCI_DEVICE 0x9f
+
+#define ISA_MODE 0x00
+#define PCI_MODE 0x01
+
+#define SBNI_IO_EXTENT 4
+
+enum sbni_reg {
+ CSR0 = 0,
+ CSR1 = 1,
+ DAT = 2
+};
+
+/* CSR0 mapping */
+enum {
+ BU_EMP = 0x02,
+ RC_CHK = 0x04,
+ CT_ZER = 0x08,
+ TR_REQ = 0x10,
+ TR_RDY = 0x20,
+ EN_INT = 0x40,
+ RC_RDY = 0x80
+};
+
+
+/* CSR1 mapping */
+#define PR_RES 0x80
+
+struct sbni_csr1 {
+ unsigned rxl : 5;
+ unsigned rate : 2;
+ unsigned : 1;
+};
+
+/* fields in frame header */
+#define FRAME_ACK_MASK (unsigned short)0x7000
+#define FRAME_LEN_MASK (unsigned short)0x03FF
+#define FRAME_FIRST (unsigned short)0x8000
+#define FRAME_RETRY (unsigned short)0x0800
+
+#define FRAME_SENT_BAD (unsigned short)0x4000
+#define FRAME_SENT_OK (unsigned short)0x3000
+
+
+/* state flags */
+enum {
+ FL_WAIT_ACK = 0x01,
+ FL_NEED_RESEND = 0x02,
+ FL_PREV_OK = 0x04,
+ FL_SLOW_MODE = 0x08,
+ FL_SECONDARY = 0x10,
+#ifdef CONFIG_SBNI_MULTILINE
+ FL_SLAVE = 0x20,
+#endif
+ FL_LINE_DOWN = 0x40
+};
+
+
+enum {
+ DEFAULT_IOBASEADDR = 0x210,
+ DEFAULT_INTERRUPTNUMBER = 5,
+ DEFAULT_RATE = 0,
+ DEFAULT_FRAME_LEN = 1012
+};
+
+#define DEF_RXL_DELTA -1
+#define DEF_RXL 0xf
+
+#define SBNI_SIG 0x5a
+
+#define SBNI_MIN_LEN 60 /* Shortest Ethernet frame without FCS */
+#define SBNI_MAX_FRAME 1023
+#define ETHER_MAX_LEN 1518
+
+#define SBNI_TIMEOUT (HZ/10)
+
+#define TR_ERROR_COUNT 32
+#define CHANGE_LEVEL_START_TICKS 4
+
+#define SBNI_MAX_NUM_CARDS 16
+
+/* internal SBNI-specific statistics */
+struct sbni_in_stats {
+ u32 all_rx_number;
+ u32 bad_rx_number;
+ u32 timeout_number;
+ u32 all_tx_number;
+ u32 resend_tx_number;
+};
+
+/* SBNI ioctl params */
+#define SIOCDEVGETINSTATS SIOCDEVPRIVATE
+#define SIOCDEVRESINSTATS SIOCDEVPRIVATE+1
+#define SIOCDEVGHWSTATE SIOCDEVPRIVATE+2
+#define SIOCDEVSHWSTATE SIOCDEVPRIVATE+3
+#define SIOCDEVENSLAVE SIOCDEVPRIVATE+4
+#define SIOCDEVEMANSIPATE SIOCDEVPRIVATE+5
+
+
+/* data packet for SIOCDEVGHWSTATE/SIOCDEVSHWSTATE ioctl requests */
+struct sbni_flags {
+ u32 rxl : 4;
+ u32 rate : 2;
+ u32 fixed_rxl : 1;
+ u32 slow_mode : 1;
+ u32 mac_addr : 24;
+};
+
+/*
+ * CRC-32 stuff
+ */
+#define CRC32(c,crc) (crc32tab[((size_t)(crc) ^ (c)) & 0xff] ^ (((crc) >> 8) & 0x00FFFFFF))
+ /* CRC generator 0xEDB88320 */
+ /* CRC remainder 0x2144DF1C */
+ /* CRC initial value 0x00000000 */
+#define CRC32_REMAINDER 0x2144DF1C
+#define CRC32_INITIAL 0x00000000
+
+#ifndef __initdata
+#define __initdata
+#endif
+
+#endif
+
diff --git a/drivers/net/wan/sdla.c b/drivers/net/wan/sdla.c
new file mode 100644
index 00000000000..3ac9a45b20f
--- /dev/null
+++ b/drivers/net/wan/sdla.c
@@ -0,0 +1,1676 @@
+/*
+ * SDLA An implementation of a driver for the Sangoma S502/S508 series
+ * multi-protocol PC interface card. Initial offering is with
+ * the DLCI driver, providing Frame Relay support for linux.
+ *
+ * Global definitions for the Frame relay interface.
+ *
+ * Version: @(#)sdla.c 0.30 12 Sep 1996
+ *
+ * Credits: Sangoma Technologies, for the use of 2 cards for an extended
+ * period of time.
+ * David Mandelstam <dm@sangoma.com> for getting me started on
+ * this project, and incentive to complete it.
+ * Gene Kozen <74604.152@compuserve.com> for providing me with
+ * important information about the cards.
+ *
+ * Author: Mike McLagan <mike.mclagan@linux.org>
+ *
+ * Changes:
+ * 0.15 Mike McLagan Improved error handling, packet dropping
+ * 0.20 Mike McLagan New transmit/receive flags for config
+ * If in FR mode, don't accept packets from
+ * non DLCI devices.
+ * 0.25 Mike McLagan Fixed problem with rejecting packets
+ * from non DLCI devices.
+ * 0.30 Mike McLagan Fixed kernel panic when used with modified
+ * ifconfig
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h> /* for CONFIG_DLCI_MAX */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/if_frad.h>
+#include <linux/sdla.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+
+static const char* version = "SDLA driver v0.30, 12 Sep 1996, mike.mclagan@linux.org";
+
+static unsigned int valid_port[] __initdata = { 0x250, 0x270, 0x280, 0x300, 0x350, 0x360, 0x380, 0x390};
+
+static unsigned int valid_mem[] __initdata = {
+ 0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000, 0xAE000,
+ 0xB0000, 0xB2000, 0xB4000, 0xB6000, 0xB8000, 0xBA000, 0xBC000, 0xBE000,
+ 0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000, 0xCE000,
+ 0xD0000, 0xD2000, 0xD4000, 0xD6000, 0xD8000, 0xDA000, 0xDC000, 0xDE000,
+ 0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000, 0xEE000};
+
+static DEFINE_SPINLOCK(sdla_lock);
+
+/*********************************************************
+ *
+ * these are the core routines that access the card itself
+ *
+ *********************************************************/
+
+#define SDLA_WINDOW(dev,addr) outb((((addr) >> 13) & 0x1F), (dev)->base_addr + SDLA_REG_Z80_WINDOW)
+
+static void __sdla_read(struct net_device *dev, int addr, void *buf, short len)
+{
+ char *temp;
+ const void *base;
+ int offset, bytes;
+
+ temp = buf;
+ while(len)
+ {
+ offset = addr & SDLA_ADDR_MASK;
+ bytes = offset + len > SDLA_WINDOW_SIZE ? SDLA_WINDOW_SIZE - offset : len;
+ base = (const void *) (dev->mem_start + offset);
+
+ SDLA_WINDOW(dev, addr);
+ memcpy(temp, base, bytes);
+
+ addr += bytes;
+ temp += bytes;
+ len -= bytes;
+ }
+}
+
+static void sdla_read(struct net_device *dev, int addr, void *buf, short len)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&sdla_lock, flags);
+ __sdla_read(dev, addr, buf, len);
+ spin_unlock_irqrestore(&sdla_lock, flags);
+}
+
+static void __sdla_write(struct net_device *dev, int addr,
+ const void *buf, short len)
+{
+ const char *temp;
+ void *base;
+ int offset, bytes;
+
+ temp = buf;
+ while(len)
+ {
+ offset = addr & SDLA_ADDR_MASK;
+ bytes = offset + len > SDLA_WINDOW_SIZE ? SDLA_WINDOW_SIZE - offset : len;
+ base = (void *) (dev->mem_start + offset);
+
+ SDLA_WINDOW(dev, addr);
+ memcpy(base, temp, bytes);
+
+ addr += bytes;
+ temp += bytes;
+ len -= bytes;
+ }
+}
+
+static void sdla_write(struct net_device *dev, int addr,
+ const void *buf, short len)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sdla_lock, flags);
+ __sdla_write(dev, addr, buf, len);
+ spin_unlock_irqrestore(&sdla_lock, flags);
+}
+
+
+static void sdla_clear(struct net_device *dev)
+{
+ unsigned long flags;
+ char *base;
+ int len, addr, bytes;
+
+ len = 65536;
+ addr = 0;
+ bytes = SDLA_WINDOW_SIZE;
+ base = (void *) dev->mem_start;
+
+ spin_lock_irqsave(&sdla_lock, flags);
+ while(len)
+ {
+ SDLA_WINDOW(dev, addr);
+ memset(base, 0, bytes);
+
+ addr += bytes;
+ len -= bytes;
+ }
+ spin_unlock_irqrestore(&sdla_lock, flags);
+
+}
+
+static char sdla_byte(struct net_device *dev, int addr)
+{
+ unsigned long flags;
+ char byte, *temp;
+
+ temp = (void *) (dev->mem_start + (addr & SDLA_ADDR_MASK));
+
+ spin_lock_irqsave(&sdla_lock, flags);
+ SDLA_WINDOW(dev, addr);
+ byte = *temp;
+ spin_unlock_irqrestore(&sdla_lock, flags);
+
+ return(byte);
+}
+
+void sdla_stop(struct net_device *dev)
+{
+ struct frad_local *flp;
+
+ flp = dev->priv;
+ switch(flp->type)
+ {
+ case SDLA_S502A:
+ outb(SDLA_S502A_HALT, dev->base_addr + SDLA_REG_CONTROL);
+ flp->state = SDLA_HALT;
+ break;
+ case SDLA_S502E:
+ outb(SDLA_HALT, dev->base_addr + SDLA_REG_Z80_CONTROL);
+ outb(SDLA_S502E_ENABLE, dev->base_addr + SDLA_REG_CONTROL);
+ flp->state = SDLA_S502E_ENABLE;
+ break;
+ case SDLA_S507:
+ flp->state &= ~SDLA_CPUEN;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ break;
+ case SDLA_S508:
+ flp->state &= ~SDLA_CPUEN;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ break;
+ }
+}
+
+void sdla_start(struct net_device *dev)
+{
+ struct frad_local *flp;
+
+ flp = dev->priv;
+ switch(flp->type)
+ {
+ case SDLA_S502A:
+ outb(SDLA_S502A_NMI, dev->base_addr + SDLA_REG_CONTROL);
+ outb(SDLA_S502A_START, dev->base_addr + SDLA_REG_CONTROL);
+ flp->state = SDLA_S502A_START;
+ break;
+ case SDLA_S502E:
+ outb(SDLA_S502E_CPUEN, dev->base_addr + SDLA_REG_Z80_CONTROL);
+ outb(0x00, dev->base_addr + SDLA_REG_CONTROL);
+ flp->state = 0;
+ break;
+ case SDLA_S507:
+ flp->state |= SDLA_CPUEN;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ break;
+ case SDLA_S508:
+ flp->state |= SDLA_CPUEN;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ break;
+ }
+}
+
+/****************************************************
+ *
+ * this is used for the S502A/E cards to determine
+ * the speed of the onboard CPU. Calibration is
+ * necessary for the Frame Relay code uploaded
+ * later. Incorrect results cause timing problems
+ * with link checks & status messages
+ *
+ ***************************************************/
+
+int sdla_z80_poll(struct net_device *dev, int z80_addr, int jiffs, char resp1, char resp2)
+{
+ unsigned long start, done, now;
+ char resp, *temp;
+
+ start = now = jiffies;
+ done = jiffies + jiffs;
+
+ temp = (void *)dev->mem_start;
+ temp += z80_addr & SDLA_ADDR_MASK;
+
+ resp = ~resp1;
+ while (time_before(jiffies, done) && (resp != resp1) && (!resp2 || (resp != resp2)))
+ {
+ if (jiffies != now)
+ {
+ SDLA_WINDOW(dev, z80_addr);
+ now = jiffies;
+ resp = *temp;
+ }
+ }
+ return(time_before(jiffies, done) ? jiffies - start : -1);
+}
+
+/* constants for Z80 CPU speed */
+#define Z80_READY '1' /* Z80 is ready to begin */
+#define LOADER_READY '2' /* driver is ready to begin */
+#define Z80_SCC_OK '3' /* SCC is on board */
+#define Z80_SCC_BAD '4' /* SCC was not found */
+
+static int sdla_cpuspeed(struct net_device *dev, struct ifreq *ifr)
+{
+ int jiffs;
+ char data;
+
+ sdla_start(dev);
+ if (sdla_z80_poll(dev, 0, 3*HZ, Z80_READY, 0) < 0)
+ return(-EIO);
+
+ data = LOADER_READY;
+ sdla_write(dev, 0, &data, 1);
+
+ if ((jiffs = sdla_z80_poll(dev, 0, 8*HZ, Z80_SCC_OK, Z80_SCC_BAD)) < 0)
+ return(-EIO);
+
+ sdla_stop(dev);
+ sdla_read(dev, 0, &data, 1);
+
+ if (data == Z80_SCC_BAD)
+ {
+ printk("%s: SCC bad\n", dev->name);
+ return(-EIO);
+ }
+
+ if (data != Z80_SCC_OK)
+ return(-EINVAL);
+
+ if (jiffs < 165)
+ ifr->ifr_mtu = SDLA_CPU_16M;
+ else if (jiffs < 220)
+ ifr->ifr_mtu = SDLA_CPU_10M;
+ else if (jiffs < 258)
+ ifr->ifr_mtu = SDLA_CPU_8M;
+ else if (jiffs < 357)
+ ifr->ifr_mtu = SDLA_CPU_7M;
+ else if (jiffs < 467)
+ ifr->ifr_mtu = SDLA_CPU_5M;
+ else
+ ifr->ifr_mtu = SDLA_CPU_3M;
+
+ return(0);
+}
+
+/************************************************
+ *
+ * Direct interaction with the Frame Relay code
+ * starts here.
+ *
+ ************************************************/
+
+struct _dlci_stat
+{
+ short dlci __attribute__((packed));
+ char flags __attribute__((packed));
+};
+
+struct _frad_stat
+{
+ char flags;
+ struct _dlci_stat dlcis[SDLA_MAX_DLCI];
+};
+
+static void sdla_errors(struct net_device *dev, int cmd, int dlci, int ret, int len, void *data)
+{
+ struct _dlci_stat *pstatus;
+ short *pdlci;
+ int i;
+ char *state, line[30];
+
+ switch (ret)
+ {
+ case SDLA_RET_MODEM:
+ state = data;
+ if (*state & SDLA_MODEM_DCD_LOW)
+ printk(KERN_INFO "%s: Modem DCD unexpectedly low!\n", dev->name);
+ if (*state & SDLA_MODEM_CTS_LOW)
+ printk(KERN_INFO "%s: Modem CTS unexpectedly low!\n", dev->name);
+ /* I should probably do something about this! */
+ break;
+
+ case SDLA_RET_CHANNEL_OFF:
+ printk(KERN_INFO "%s: Channel became inoperative!\n", dev->name);
+ /* same here */
+ break;
+
+ case SDLA_RET_CHANNEL_ON:
+ printk(KERN_INFO "%s: Channel became operative!\n", dev->name);
+ /* same here */
+ break;
+
+ case SDLA_RET_DLCI_STATUS:
+ printk(KERN_INFO "%s: Status change reported by Access Node.\n", dev->name);
+ len /= sizeof(struct _dlci_stat);
+ for(pstatus = data, i=0;i < len;i++,pstatus++)
+ {
+ if (pstatus->flags & SDLA_DLCI_NEW)
+ state = "new";
+ else if (pstatus->flags & SDLA_DLCI_DELETED)
+ state = "deleted";
+ else if (pstatus->flags & SDLA_DLCI_ACTIVE)
+ state = "active";
+ else
+ {
+ sprintf(line, "unknown status: %02X", pstatus->flags);
+ state = line;
+ }
+ printk(KERN_INFO "%s: DLCI %i: %s.\n", dev->name, pstatus->dlci, state);
+ /* same here */
+ }
+ break;
+
+ case SDLA_RET_DLCI_UNKNOWN:
+ printk(KERN_INFO "%s: Received unknown DLCIs:", dev->name);
+ len /= sizeof(short);
+ for(pdlci = data,i=0;i < len;i++,pdlci++)
+ printk(" %i", *pdlci);
+ printk("\n");
+ break;
+
+ case SDLA_RET_TIMEOUT:
+ printk(KERN_ERR "%s: Command timed out!\n", dev->name);
+ break;
+
+ case SDLA_RET_BUF_OVERSIZE:
+ printk(KERN_INFO "%s: Bc/CIR overflow, acceptable size is %i\n", dev->name, len);
+ break;
+
+ case SDLA_RET_BUF_TOO_BIG:
+ printk(KERN_INFO "%s: Buffer size over specified max of %i\n", dev->name, len);
+ break;
+
+ case SDLA_RET_CHANNEL_INACTIVE:
+ case SDLA_RET_DLCI_INACTIVE:
+ case SDLA_RET_CIR_OVERFLOW:
+ case SDLA_RET_NO_BUFS:
+ if (cmd == SDLA_INFORMATION_WRITE)
+ break;
+
+ default:
+ printk(KERN_DEBUG "%s: Cmd 0x%2.2X generated return code 0x%2.2X\n", dev->name, cmd, ret);
+ /* Further processing could be done here */
+ break;
+ }
+}
+
+static int sdla_cmd(struct net_device *dev, int cmd, short dlci, short flags,
+ void *inbuf, short inlen, void *outbuf, short *outlen)
+{
+ static struct _frad_stat status;
+ struct frad_local *flp;
+ struct sdla_cmd *cmd_buf;
+ unsigned long pflags;
+ unsigned long jiffs;
+ int ret, waiting, len;
+ long window;
+
+ flp = dev->priv;
+ window = flp->type == SDLA_S508 ? SDLA_508_CMD_BUF : SDLA_502_CMD_BUF;
+ cmd_buf = (struct sdla_cmd *)(dev->mem_start + (window & SDLA_ADDR_MASK));
+ ret = 0;
+ len = 0;
+ jiffs = jiffies + HZ; /* 1 second is plenty */
+
+ spin_lock_irqsave(&sdla_lock, pflags);
+ SDLA_WINDOW(dev, window);
+ cmd_buf->cmd = cmd;
+ cmd_buf->dlci = dlci;
+ cmd_buf->flags = flags;
+
+ if (inbuf)
+ memcpy(cmd_buf->data, inbuf, inlen);
+
+ cmd_buf->length = inlen;
+
+ cmd_buf->opp_flag = 1;
+ spin_unlock_irqrestore(&sdla_lock, pflags);
+
+ waiting = 1;
+ len = 0;
+ while (waiting && time_before_eq(jiffies, jiffs))
+ {
+ if (waiting++ % 3)
+ {
+ spin_lock_irqsave(&sdla_lock, pflags);
+ SDLA_WINDOW(dev, window);
+ waiting = ((volatile int)(cmd_buf->opp_flag));
+ spin_unlock_irqrestore(&sdla_lock, pflags);
+ }
+ }
+
+ if (!waiting)
+ {
+
+ spin_lock_irqsave(&sdla_lock, pflags);
+ SDLA_WINDOW(dev, window);
+ ret = cmd_buf->retval;
+ len = cmd_buf->length;
+ if (outbuf && outlen)
+ {
+ *outlen = *outlen >= len ? len : *outlen;
+
+ if (*outlen)
+ memcpy(outbuf, cmd_buf->data, *outlen);
+ }
+
+ /* This is a local copy that's used for error handling */
+ if (ret)
+ memcpy(&status, cmd_buf->data, len > sizeof(status) ? sizeof(status) : len);
+
+ spin_unlock_irqrestore(&sdla_lock, pflags);
+ }
+ else
+ ret = SDLA_RET_TIMEOUT;
+
+ if (ret != SDLA_RET_OK)
+ sdla_errors(dev, cmd, dlci, ret, len, &status);
+
+ return(ret);
+}
+
+/***********************************************
+ *
+ * these functions are called by the DLCI driver
+ *
+ ***********************************************/
+
+static int sdla_reconfig(struct net_device *dev);
+
+int sdla_activate(struct net_device *slave, struct net_device *master)
+{
+ struct frad_local *flp;
+ int i;
+
+ flp = slave->priv;
+
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->master[i] == master)
+ break;
+
+ if (i == CONFIG_DLCI_MAX)
+ return(-ENODEV);
+
+ flp->dlci[i] = abs(flp->dlci[i]);
+
+ if (netif_running(slave) && (flp->config.station == FRAD_STATION_NODE))
+ sdla_cmd(slave, SDLA_ACTIVATE_DLCI, 0, 0, &flp->dlci[i], sizeof(short), NULL, NULL);
+
+ return(0);
+}
+
+int sdla_deactivate(struct net_device *slave, struct net_device *master)
+{
+ struct frad_local *flp;
+ int i;
+
+ flp = slave->priv;
+
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->master[i] == master)
+ break;
+
+ if (i == CONFIG_DLCI_MAX)
+ return(-ENODEV);
+
+ flp->dlci[i] = -abs(flp->dlci[i]);
+
+ if (netif_running(slave) && (flp->config.station == FRAD_STATION_NODE))
+ sdla_cmd(slave, SDLA_DEACTIVATE_DLCI, 0, 0, &flp->dlci[i], sizeof(short), NULL, NULL);
+
+ return(0);
+}
+
+int sdla_assoc(struct net_device *slave, struct net_device *master)
+{
+ struct frad_local *flp;
+ int i;
+
+ if (master->type != ARPHRD_DLCI)
+ return(-EINVAL);
+
+ flp = slave->priv;
+
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ {
+ if (!flp->master[i])
+ break;
+ if (abs(flp->dlci[i]) == *(short *)(master->dev_addr))
+ return(-EADDRINUSE);
+ }
+
+ if (i == CONFIG_DLCI_MAX)
+ return(-EMLINK); /* #### Alan: Comments on this ?? */
+
+
+ flp->master[i] = master;
+ flp->dlci[i] = -*(short *)(master->dev_addr);
+ master->mtu = slave->mtu;
+
+ if (netif_running(slave)) {
+ if (flp->config.station == FRAD_STATION_CPE)
+ sdla_reconfig(slave);
+ else
+ sdla_cmd(slave, SDLA_ADD_DLCI, 0, 0, master->dev_addr, sizeof(short), NULL, NULL);
+ }
+
+ return(0);
+}
+
+int sdla_deassoc(struct net_device *slave, struct net_device *master)
+{
+ struct frad_local *flp;
+ int i;
+
+ flp = slave->priv;
+
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->master[i] == master)
+ break;
+
+ if (i == CONFIG_DLCI_MAX)
+ return(-ENODEV);
+
+ flp->master[i] = NULL;
+ flp->dlci[i] = 0;
+
+
+ if (netif_running(slave)) {
+ if (flp->config.station == FRAD_STATION_CPE)
+ sdla_reconfig(slave);
+ else
+ sdla_cmd(slave, SDLA_DELETE_DLCI, 0, 0, master->dev_addr, sizeof(short), NULL, NULL);
+ }
+
+ return(0);
+}
+
+int sdla_dlci_conf(struct net_device *slave, struct net_device *master, int get)
+{
+ struct frad_local *flp;
+ struct dlci_local *dlp;
+ int i;
+ short len, ret;
+
+ flp = slave->priv;
+
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->master[i] == master)
+ break;
+
+ if (i == CONFIG_DLCI_MAX)
+ return(-ENODEV);
+
+ dlp = master->priv;
+
+ ret = SDLA_RET_OK;
+ len = sizeof(struct dlci_conf);
+ if (netif_running(slave)) {
+ if (get)
+ ret = sdla_cmd(slave, SDLA_READ_DLCI_CONFIGURATION, abs(flp->dlci[i]), 0,
+ NULL, 0, &dlp->config, &len);
+ else
+ ret = sdla_cmd(slave, SDLA_SET_DLCI_CONFIGURATION, abs(flp->dlci[i]), 0,
+ &dlp->config, sizeof(struct dlci_conf) - 4 * sizeof(short), NULL, NULL);
+ }
+
+ return(ret == SDLA_RET_OK ? 0 : -EIO);
+}
+
+/**************************
+ *
+ * now for the Linux driver
+ *
+ **************************/
+
+/* NOTE: the DLCI driver deals with freeing the SKB!! */
+static int sdla_transmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct frad_local *flp;
+ int ret, addr, accept, i;
+ short size;
+ unsigned long flags;
+ struct buf_entry *pbuf;
+
+ flp = dev->priv;
+ ret = 0;
+ accept = 1;
+
+ netif_stop_queue(dev);
+
+ /*
+ * stupid GateD insists on setting up the multicast router thru us
+ * and we're ill equipped to handle a non Frame Relay packet at this
+ * time!
+ */
+
+ accept = 1;
+ switch (dev->type)
+ {
+ case ARPHRD_FRAD:
+ if (skb->dev->type != ARPHRD_DLCI)
+ {
+ printk(KERN_WARNING "%s: Non DLCI device, type %i, tried to send on FRAD module.\n", dev->name, skb->dev->type);
+ accept = 0;
+ }
+ break;
+ default:
+ printk(KERN_WARNING "%s: unknown firmware type 0x%4.4X\n", dev->name, dev->type);
+ accept = 0;
+ break;
+ }
+ if (accept)
+ {
+ /* this is frame specific, but till there's a PPP module, it's the default */
+ switch (flp->type)
+ {
+ case SDLA_S502A:
+ case SDLA_S502E:
+ ret = sdla_cmd(dev, SDLA_INFORMATION_WRITE, *(short *)(skb->dev->dev_addr), 0, skb->data, skb->len, NULL, NULL);
+ break;
+ case SDLA_S508:
+ size = sizeof(addr);
+ ret = sdla_cmd(dev, SDLA_INFORMATION_WRITE, *(short *)(skb->dev->dev_addr), 0, NULL, skb->len, &addr, &size);
+ if (ret == SDLA_RET_OK)
+ {
+
+ spin_lock_irqsave(&sdla_lock, flags);
+ SDLA_WINDOW(dev, addr);
+ pbuf = (void *)(((int) dev->mem_start) + (addr & SDLA_ADDR_MASK));
+ __sdla_write(dev, pbuf->buf_addr, skb->data, skb->len);
+ SDLA_WINDOW(dev, addr);
+ pbuf->opp_flag = 1;
+ spin_unlock_irqrestore(&sdla_lock, flags);
+ }
+ break;
+ }
+ switch (ret)
+ {
+ case SDLA_RET_OK:
+ flp->stats.tx_packets++;
+ ret = DLCI_RET_OK;
+ break;
+
+ case SDLA_RET_CIR_OVERFLOW:
+ case SDLA_RET_BUF_OVERSIZE:
+ case SDLA_RET_NO_BUFS:
+ flp->stats.tx_dropped++;
+ ret = DLCI_RET_DROP;
+ break;
+
+ default:
+ flp->stats.tx_errors++;
+ ret = DLCI_RET_ERR;
+ break;
+ }
+ }
+ netif_wake_queue(dev);
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ {
+ if(flp->master[i]!=NULL)
+ netif_wake_queue(flp->master[i]);
+ }
+ return(ret);
+}
+
+static void sdla_receive(struct net_device *dev)
+{
+ struct net_device *master;
+ struct frad_local *flp;
+ struct dlci_local *dlp;
+ struct sk_buff *skb;
+
+ struct sdla_cmd *cmd;
+ struct buf_info *pbufi;
+ struct buf_entry *pbuf;
+
+ unsigned long flags;
+ int i=0, received, success, addr, buf_base, buf_top;
+ short dlci, len, len2, split;
+
+ flp = dev->priv;
+ success = 1;
+ received = addr = buf_top = buf_base = 0;
+ len = dlci = 0;
+ skb = NULL;
+ master = NULL;
+ cmd = NULL;
+ pbufi = NULL;
+ pbuf = NULL;
+
+ spin_lock_irqsave(&sdla_lock, flags);
+
+ switch (flp->type)
+ {
+ case SDLA_S502A:
+ case SDLA_S502E:
+ cmd = (void *) (dev->mem_start + (SDLA_502_RCV_BUF & SDLA_ADDR_MASK));
+ SDLA_WINDOW(dev, SDLA_502_RCV_BUF);
+ success = cmd->opp_flag;
+ if (!success)
+ break;
+
+ dlci = cmd->dlci;
+ len = cmd->length;
+ break;
+
+ case SDLA_S508:
+ pbufi = (void *) (dev->mem_start + (SDLA_508_RXBUF_INFO & SDLA_ADDR_MASK));
+ SDLA_WINDOW(dev, SDLA_508_RXBUF_INFO);
+ pbuf = (void *) (dev->mem_start + ((pbufi->rse_base + flp->buffer * sizeof(struct buf_entry)) & SDLA_ADDR_MASK));
+ success = pbuf->opp_flag;
+ if (!success)
+ break;
+
+ buf_top = pbufi->buf_top;
+ buf_base = pbufi->buf_base;
+ dlci = pbuf->dlci;
+ len = pbuf->length;
+ addr = pbuf->buf_addr;
+ break;
+ }
+
+ /* common code, find the DLCI and get the SKB */
+ if (success)
+ {
+ for (i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i] == dlci)
+ break;
+
+ if (i == CONFIG_DLCI_MAX)
+ {
+ printk(KERN_NOTICE "%s: Received packet from invalid DLCI %i, ignoring.", dev->name, dlci);
+ flp->stats.rx_errors++;
+ success = 0;
+ }
+ }
+
+ if (success)
+ {
+ master = flp->master[i];
+ skb = dev_alloc_skb(len + sizeof(struct frhdr));
+ if (skb == NULL)
+ {
+ printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", dev->name);
+ flp->stats.rx_dropped++;
+ success = 0;
+ }
+ else
+ skb_reserve(skb, sizeof(struct frhdr));
+ }
+
+ /* pick up the data */
+ switch (flp->type)
+ {
+ case SDLA_S502A:
+ case SDLA_S502E:
+ if (success)
+ __sdla_read(dev, SDLA_502_RCV_BUF + SDLA_502_DATA_OFS, skb_put(skb,len), len);
+
+ SDLA_WINDOW(dev, SDLA_502_RCV_BUF);
+ cmd->opp_flag = 0;
+ break;
+
+ case SDLA_S508:
+ if (success)
+ {
+ /* is this buffer split off the end of the internal ring buffer */
+ split = addr + len > buf_top + 1 ? len - (buf_top - addr + 1) : 0;
+ len2 = len - split;
+
+ __sdla_read(dev, addr, skb_put(skb, len2), len2);
+ if (split)
+ __sdla_read(dev, buf_base, skb_put(skb, split), split);
+ }
+
+ /* increment the buffer we're looking at */
+ SDLA_WINDOW(dev, SDLA_508_RXBUF_INFO);
+ flp->buffer = (flp->buffer + 1) % pbufi->rse_num;
+ pbuf->opp_flag = 0;
+ break;
+ }
+
+ if (success)
+ {
+ flp->stats.rx_packets++;
+ dlp = master->priv;
+ (*dlp->receive)(skb, master);
+ }
+
+ spin_unlock_irqrestore(&sdla_lock, flags);
+}
+
+static irqreturn_t sdla_isr(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct net_device *dev;
+ struct frad_local *flp;
+ char byte;
+
+ dev = dev_id;
+
+ if (dev == NULL)
+ {
+ printk(KERN_WARNING "sdla_isr(): irq %d for unknown device.\n", irq);
+ return IRQ_NONE;
+ }
+
+ flp = dev->priv;
+
+ if (!flp->initialized)
+ {
+ printk(KERN_WARNING "%s: irq %d for uninitialized device.\n", dev->name, irq);
+ return IRQ_NONE;
+ }
+
+ byte = sdla_byte(dev, flp->type == SDLA_S508 ? SDLA_508_IRQ_INTERFACE : SDLA_502_IRQ_INTERFACE);
+ switch (byte)
+ {
+ case SDLA_INTR_RX:
+ sdla_receive(dev);
+ break;
+
+ /* the command will get an error return, which is processed above */
+ case SDLA_INTR_MODEM:
+ case SDLA_INTR_STATUS:
+ sdla_cmd(dev, SDLA_READ_DLC_STATUS, 0, 0, NULL, 0, NULL, NULL);
+ break;
+
+ case SDLA_INTR_TX:
+ case SDLA_INTR_COMPLETE:
+ case SDLA_INTR_TIMER:
+ printk(KERN_WARNING "%s: invalid irq flag 0x%02X.\n", dev->name, byte);
+ break;
+ }
+
+ /* the S502E requires a manual acknowledgement of the interrupt */
+ if (flp->type == SDLA_S502E)
+ {
+ flp->state &= ~SDLA_S502E_INTACK;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ flp->state |= SDLA_S502E_INTACK;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ }
+
+ /* this clears the byte, informing the Z80 we're done */
+ byte = 0;
+ sdla_write(dev, flp->type == SDLA_S508 ? SDLA_508_IRQ_INTERFACE : SDLA_502_IRQ_INTERFACE, &byte, sizeof(byte));
+ return IRQ_HANDLED;
+}
+
+static void sdla_poll(unsigned long device)
+{
+ struct net_device *dev;
+ struct frad_local *flp;
+
+ dev = (struct net_device *) device;
+ flp = dev->priv;
+
+ if (sdla_byte(dev, SDLA_502_RCV_BUF))
+ sdla_receive(dev);
+
+ flp->timer.expires = 1;
+ add_timer(&flp->timer);
+}
+
+static int sdla_close(struct net_device *dev)
+{
+ struct frad_local *flp;
+ struct intr_info intr;
+ int len, i;
+ short dlcis[CONFIG_DLCI_MAX];
+
+ flp = dev->priv;
+
+ len = 0;
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i])
+ dlcis[len++] = abs(flp->dlci[i]);
+ len *= 2;
+
+ if (flp->config.station == FRAD_STATION_NODE)
+ {
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i] > 0)
+ sdla_cmd(dev, SDLA_DEACTIVATE_DLCI, 0, 0, dlcis, len, NULL, NULL);
+ sdla_cmd(dev, SDLA_DELETE_DLCI, 0, 0, &flp->dlci[i], sizeof(flp->dlci[i]), NULL, NULL);
+ }
+
+ memset(&intr, 0, sizeof(intr));
+ /* let's start up the reception */
+ switch(flp->type)
+ {
+ case SDLA_S502A:
+ del_timer(&flp->timer);
+ break;
+
+ case SDLA_S502E:
+ sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(char) + sizeof(short), NULL, NULL);
+ flp->state &= ~SDLA_S502E_INTACK;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ break;
+
+ case SDLA_S507:
+ break;
+
+ case SDLA_S508:
+ sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(struct intr_info), NULL, NULL);
+ flp->state &= ~SDLA_S508_INTEN;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ break;
+ }
+
+ sdla_cmd(dev, SDLA_DISABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+
+ netif_stop_queue(dev);
+
+ return(0);
+}
+
+struct conf_data {
+ struct frad_conf config;
+ short dlci[CONFIG_DLCI_MAX];
+};
+
+static int sdla_open(struct net_device *dev)
+{
+ struct frad_local *flp;
+ struct dlci_local *dlp;
+ struct conf_data data;
+ struct intr_info intr;
+ int len, i;
+ char byte;
+
+ flp = dev->priv;
+
+ if (!flp->initialized)
+ return(-EPERM);
+
+ if (!flp->configured)
+ return(-EPERM);
+
+ /* time to send in the configuration */
+ len = 0;
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i])
+ data.dlci[len++] = abs(flp->dlci[i]);
+ len *= 2;
+
+ memcpy(&data.config, &flp->config, sizeof(struct frad_conf));
+ len += sizeof(struct frad_conf);
+
+ sdla_cmd(dev, SDLA_DISABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+ sdla_cmd(dev, SDLA_SET_DLCI_CONFIGURATION, 0, 0, &data, len, NULL, NULL);
+
+ if (flp->type == SDLA_S508)
+ flp->buffer = 0;
+
+ sdla_cmd(dev, SDLA_ENABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+
+ /* let's start up the reception */
+ memset(&intr, 0, sizeof(intr));
+ switch(flp->type)
+ {
+ case SDLA_S502A:
+ flp->timer.expires = 1;
+ add_timer(&flp->timer);
+ break;
+
+ case SDLA_S502E:
+ flp->state |= SDLA_S502E_ENABLE;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ flp->state |= SDLA_S502E_INTACK;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ byte = 0;
+ sdla_write(dev, SDLA_502_IRQ_INTERFACE, &byte, sizeof(byte));
+ intr.flags = SDLA_INTR_RX | SDLA_INTR_STATUS | SDLA_INTR_MODEM;
+ sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(char) + sizeof(short), NULL, NULL);
+ break;
+
+ case SDLA_S507:
+ break;
+
+ case SDLA_S508:
+ flp->state |= SDLA_S508_INTEN;
+ outb(flp->state, dev->base_addr + SDLA_REG_CONTROL);
+ byte = 0;
+ sdla_write(dev, SDLA_508_IRQ_INTERFACE, &byte, sizeof(byte));
+ intr.flags = SDLA_INTR_RX | SDLA_INTR_STATUS | SDLA_INTR_MODEM;
+ intr.irq = dev->irq;
+ sdla_cmd(dev, SDLA_SET_IRQ_TRIGGER, 0, 0, &intr, sizeof(struct intr_info), NULL, NULL);
+ break;
+ }
+
+ if (flp->config.station == FRAD_STATION_CPE)
+ {
+ byte = SDLA_ICS_STATUS_ENQ;
+ sdla_cmd(dev, SDLA_ISSUE_IN_CHANNEL_SIGNAL, 0, 0, &byte, sizeof(byte), NULL, NULL);
+ }
+ else
+ {
+ sdla_cmd(dev, SDLA_ADD_DLCI, 0, 0, data.dlci, len - sizeof(struct frad_conf), NULL, NULL);
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i] > 0)
+ sdla_cmd(dev, SDLA_ACTIVATE_DLCI, 0, 0, &flp->dlci[i], 2*sizeof(flp->dlci[i]), NULL, NULL);
+ }
+
+ /* configure any specific DLCI settings */
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i])
+ {
+ dlp = flp->master[i]->priv;
+ if (dlp->configured)
+ sdla_cmd(dev, SDLA_SET_DLCI_CONFIGURATION, abs(flp->dlci[i]), 0, &dlp->config, sizeof(struct dlci_conf), NULL, NULL);
+ }
+
+ netif_start_queue(dev);
+
+ return(0);
+}
+
+static int sdla_config(struct net_device *dev, struct frad_conf __user *conf, int get)
+{
+ struct frad_local *flp;
+ struct conf_data data;
+ int i;
+ short size;
+
+ if (dev->type == 0xFFFF)
+ return(-EUNATCH);
+
+ flp = dev->priv;
+
+ if (!get)
+ {
+ if (netif_running(dev))
+ return(-EBUSY);
+
+ if(copy_from_user(&data.config, conf, sizeof(struct frad_conf)))
+ return -EFAULT;
+
+ if (data.config.station & ~FRAD_STATION_NODE)
+ return(-EINVAL);
+
+ if (data.config.flags & ~FRAD_VALID_FLAGS)
+ return(-EINVAL);
+
+ if ((data.config.kbaud < 0) ||
+ ((data.config.kbaud > 128) && (flp->type != SDLA_S508)))
+ return(-EINVAL);
+
+ if (data.config.clocking & ~(FRAD_CLOCK_INT | SDLA_S508_PORT_RS232))
+ return(-EINVAL);
+
+ if ((data.config.mtu < 0) || (data.config.mtu > SDLA_MAX_MTU))
+ return(-EINVAL);
+
+ if ((data.config.T391 < 5) || (data.config.T391 > 30))
+ return(-EINVAL);
+
+ if ((data.config.T392 < 5) || (data.config.T392 > 30))
+ return(-EINVAL);
+
+ if ((data.config.N391 < 1) || (data.config.N391 > 255))
+ return(-EINVAL);
+
+ if ((data.config.N392 < 1) || (data.config.N392 > 10))
+ return(-EINVAL);
+
+ if ((data.config.N393 < 1) || (data.config.N393 > 10))
+ return(-EINVAL);
+
+ memcpy(&flp->config, &data.config, sizeof(struct frad_conf));
+ flp->config.flags |= SDLA_DIRECT_RECV;
+
+ if (flp->type == SDLA_S508)
+ flp->config.flags |= SDLA_TX70_RX30;
+
+ if (dev->mtu != flp->config.mtu)
+ {
+ /* this is required to change the MTU */
+ dev->mtu = flp->config.mtu;
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->master[i])
+ flp->master[i]->mtu = flp->config.mtu;
+ }
+
+ flp->config.mtu += sizeof(struct frhdr);
+
+ /* off to the races! */
+ if (!flp->configured)
+ sdla_start(dev);
+
+ flp->configured = 1;
+ }
+ else
+ {
+ /* no sense reading if the CPU isn't started */
+ if (netif_running(dev))
+ {
+ size = sizeof(data);
+ if (sdla_cmd(dev, SDLA_READ_DLCI_CONFIGURATION, 0, 0, NULL, 0, &data, &size) != SDLA_RET_OK)
+ return(-EIO);
+ }
+ else
+ if (flp->configured)
+ memcpy(&data.config, &flp->config, sizeof(struct frad_conf));
+ else
+ memset(&data.config, 0, sizeof(struct frad_conf));
+
+ memcpy(&flp->config, &data.config, sizeof(struct frad_conf));
+ data.config.flags &= FRAD_VALID_FLAGS;
+ data.config.mtu -= data.config.mtu > sizeof(struct frhdr) ? sizeof(struct frhdr) : data.config.mtu;
+ return copy_to_user(conf, &data.config, sizeof(struct frad_conf))?-EFAULT:0;
+ }
+
+ return(0);
+}
+
+static int sdla_xfer(struct net_device *dev, struct sdla_mem __user *info, int read)
+{
+ struct sdla_mem mem;
+ char *temp;
+
+ if(copy_from_user(&mem, info, sizeof(mem)))
+ return -EFAULT;
+
+ if (read)
+ {
+ temp = kmalloc(mem.len, GFP_KERNEL);
+ if (!temp)
+ return(-ENOMEM);
+ memset(temp, 0, mem.len);
+ sdla_read(dev, mem.addr, temp, mem.len);
+ if(copy_to_user(mem.data, temp, mem.len))
+ {
+ kfree(temp);
+ return -EFAULT;
+ }
+ kfree(temp);
+ }
+ else
+ {
+ temp = kmalloc(mem.len, GFP_KERNEL);
+ if (!temp)
+ return(-ENOMEM);
+ if(copy_from_user(temp, mem.data, mem.len))
+ {
+ kfree(temp);
+ return -EFAULT;
+ }
+ sdla_write(dev, mem.addr, temp, mem.len);
+ kfree(temp);
+ }
+ return(0);
+}
+
+static int sdla_reconfig(struct net_device *dev)
+{
+ struct frad_local *flp;
+ struct conf_data data;
+ int i, len;
+
+ flp = dev->priv;
+
+ len = 0;
+ for(i=0;i<CONFIG_DLCI_MAX;i++)
+ if (flp->dlci[i])
+ data.dlci[len++] = flp->dlci[i];
+ len *= 2;
+
+ memcpy(&data, &flp->config, sizeof(struct frad_conf));
+ len += sizeof(struct frad_conf);
+
+ sdla_cmd(dev, SDLA_DISABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+ sdla_cmd(dev, SDLA_SET_DLCI_CONFIGURATION, 0, 0, &data, len, NULL, NULL);
+ sdla_cmd(dev, SDLA_ENABLE_COMMUNICATIONS, 0, 0, NULL, 0, NULL, NULL);
+
+ return(0);
+}
+
+static int sdla_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct frad_local *flp;
+
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ flp = dev->priv;
+
+ if (!flp->initialized)
+ return(-EINVAL);
+
+ switch (cmd)
+ {
+ case FRAD_GET_CONF:
+ case FRAD_SET_CONF:
+ return(sdla_config(dev, ifr->ifr_data, cmd == FRAD_GET_CONF));
+
+ case SDLA_IDENTIFY:
+ ifr->ifr_flags = flp->type;
+ break;
+
+ case SDLA_CPUSPEED:
+ return(sdla_cpuspeed(dev, ifr));
+
+/* ==========================================================
+NOTE: This is rather a useless action right now, as the
+ current driver does not support protocols other than
+ FR. However, Sangoma has modules for a number of
+ other protocols in the works.
+============================================================*/
+ case SDLA_PROTOCOL:
+ if (flp->configured)
+ return(-EALREADY);
+
+ switch (ifr->ifr_flags)
+ {
+ case ARPHRD_FRAD:
+ dev->type = ifr->ifr_flags;
+ break;
+ default:
+ return(-ENOPROTOOPT);
+ }
+ break;
+
+ case SDLA_CLEARMEM:
+ sdla_clear(dev);
+ break;
+
+ case SDLA_WRITEMEM:
+ case SDLA_READMEM:
+ if(!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+ return(sdla_xfer(dev, ifr->ifr_data, cmd == SDLA_READMEM));
+
+ case SDLA_START:
+ sdla_start(dev);
+ break;
+
+ case SDLA_STOP:
+ sdla_stop(dev);
+ break;
+
+ default:
+ return(-EOPNOTSUPP);
+ }
+ return(0);
+}
+
+int sdla_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct frad_local *flp;
+
+ flp = dev->priv;
+
+ if (netif_running(dev))
+ return(-EBUSY);
+
+ /* for now, you can't change the MTU! */
+ return(-EOPNOTSUPP);
+}
+
+int sdla_set_config(struct net_device *dev, struct ifmap *map)
+{
+ struct frad_local *flp;
+ int i;
+ char byte;
+ unsigned base;
+ int err = -EINVAL;
+
+ flp = dev->priv;
+
+ if (flp->initialized)
+ return(-EINVAL);
+
+ for(i=0;i < sizeof(valid_port) / sizeof (int) ; i++)
+ if (valid_port[i] == map->base_addr)
+ break;
+
+ if (i == sizeof(valid_port) / sizeof(int))
+ return(-EINVAL);
+
+ if (!request_region(map->base_addr, SDLA_IO_EXTENTS, dev->name)){
+ printk(KERN_WARNING "SDLA: io-port 0x%04lx in use \n", dev->base_addr);
+ return(-EINVAL);
+ }
+ base = map->base_addr;
+
+ /* test for card types, S502A, S502E, S507, S508 */
+ /* these tests shut down the card completely, so clear the state */
+ flp->type = SDLA_UNKNOWN;
+ flp->state = 0;
+
+ for(i=1;i<SDLA_IO_EXTENTS;i++)
+ if (inb(base + i) != 0xFF)
+ break;
+
+ if (i == SDLA_IO_EXTENTS) {
+ outb(SDLA_HALT, base + SDLA_REG_Z80_CONTROL);
+ if ((inb(base + SDLA_S502_STS) & 0x0F) == 0x08) {
+ outb(SDLA_S502E_INTACK, base + SDLA_REG_CONTROL);
+ if ((inb(base + SDLA_S502_STS) & 0x0F) == 0x0C) {
+ outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+ flp->type = SDLA_S502E;
+ goto got_type;
+ }
+ }
+ }
+
+ for(byte=inb(base),i=0;i<SDLA_IO_EXTENTS;i++)
+ if (inb(base + i) != byte)
+ break;
+
+ if (i == SDLA_IO_EXTENTS) {
+ outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+ if ((inb(base + SDLA_S502_STS) & 0x7E) == 0x30) {
+ outb(SDLA_S507_ENABLE, base + SDLA_REG_CONTROL);
+ if ((inb(base + SDLA_S502_STS) & 0x7E) == 0x32) {
+ outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+ flp->type = SDLA_S507;
+ goto got_type;
+ }
+ }
+ }
+
+ outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+ if ((inb(base + SDLA_S508_STS) & 0x3F) == 0x00) {
+ outb(SDLA_S508_INTEN, base + SDLA_REG_CONTROL);
+ if ((inb(base + SDLA_S508_STS) & 0x3F) == 0x10) {
+ outb(SDLA_HALT, base + SDLA_REG_CONTROL);
+ flp->type = SDLA_S508;
+ goto got_type;
+ }
+ }
+
+ outb(SDLA_S502A_HALT, base + SDLA_REG_CONTROL);
+ if (inb(base + SDLA_S502_STS) == 0x40) {
+ outb(SDLA_S502A_START, base + SDLA_REG_CONTROL);
+ if (inb(base + SDLA_S502_STS) == 0x40) {
+ outb(SDLA_S502A_INTEN, base + SDLA_REG_CONTROL);
+ if (inb(base + SDLA_S502_STS) == 0x44) {
+ outb(SDLA_S502A_START, base + SDLA_REG_CONTROL);
+ flp->type = SDLA_S502A;
+ goto got_type;
+ }
+ }
+ }
+
+ printk(KERN_NOTICE "%s: Unknown card type\n", dev->name);
+ err = -ENODEV;
+ goto fail;
+
+got_type:
+ switch(base) {
+ case 0x270:
+ case 0x280:
+ case 0x380:
+ case 0x390:
+ if (flp->type != SDLA_S508 && flp->type != SDLA_S507)
+ goto fail;
+ }
+
+ switch (map->irq) {
+ case 2:
+ if (flp->type != SDLA_S502E)
+ goto fail;
+ break;
+
+ case 10:
+ case 11:
+ case 12:
+ case 15:
+ case 4:
+ if (flp->type != SDLA_S508 && flp->type != SDLA_S507)
+ goto fail;
+ break;
+ case 3:
+ case 5:
+ case 7:
+ if (flp->type == SDLA_S502A)
+ goto fail;
+ break;
+
+ default:
+ goto fail;
+ }
+
+ err = -EAGAIN;
+ if (request_irq(dev->irq, &sdla_isr, 0, dev->name, dev))
+ goto fail;
+
+ if (flp->type == SDLA_S507) {
+ switch(dev->irq) {
+ case 3:
+ flp->state = SDLA_S507_IRQ3;
+ break;
+ case 4:
+ flp->state = SDLA_S507_IRQ4;
+ break;
+ case 5:
+ flp->state = SDLA_S507_IRQ5;
+ break;
+ case 7:
+ flp->state = SDLA_S507_IRQ7;
+ break;
+ case 10:
+ flp->state = SDLA_S507_IRQ10;
+ break;
+ case 11:
+ flp->state = SDLA_S507_IRQ11;
+ break;
+ case 12:
+ flp->state = SDLA_S507_IRQ12;
+ break;
+ case 15:
+ flp->state = SDLA_S507_IRQ15;
+ break;
+ }
+ }
+
+ for(i=0;i < sizeof(valid_mem) / sizeof (int) ; i++)
+ if (valid_mem[i] == map->mem_start)
+ break;
+
+ err = -EINVAL;
+ if (i == sizeof(valid_mem) / sizeof(int))
+ goto fail2;
+
+ if (flp->type == SDLA_S502A && (map->mem_start & 0xF000) >> 12 == 0x0E)
+ goto fail2;
+
+ if (flp->type != SDLA_S507 && map->mem_start >> 16 == 0x0B)
+ goto fail2;
+
+ if (flp->type == SDLA_S507 && map->mem_start >> 16 == 0x0D)
+ goto fail2;
+
+ byte = flp->type != SDLA_S508 ? SDLA_8K_WINDOW : 0;
+ byte |= (map->mem_start & 0xF000) >> (12 + (flp->type == SDLA_S508 ? 1 : 0));
+ switch(flp->type) {
+ case SDLA_S502A:
+ case SDLA_S502E:
+ switch (map->mem_start >> 16) {
+ case 0x0A:
+ byte |= SDLA_S502_SEG_A;
+ break;
+ case 0x0C:
+ byte |= SDLA_S502_SEG_C;
+ break;
+ case 0x0D:
+ byte |= SDLA_S502_SEG_D;
+ break;
+ case 0x0E:
+ byte |= SDLA_S502_SEG_E;
+ break;
+ }
+ break;
+ case SDLA_S507:
+ switch (map->mem_start >> 16) {
+ case 0x0A:
+ byte |= SDLA_S507_SEG_A;
+ break;
+ case 0x0B:
+ byte |= SDLA_S507_SEG_B;
+ break;
+ case 0x0C:
+ byte |= SDLA_S507_SEG_C;
+ break;
+ case 0x0E:
+ byte |= SDLA_S507_SEG_E;
+ break;
+ }
+ break;
+ case SDLA_S508:
+ switch (map->mem_start >> 16) {
+ case 0x0A:
+ byte |= SDLA_S508_SEG_A;
+ break;
+ case 0x0C:
+ byte |= SDLA_S508_SEG_C;
+ break;
+ case 0x0D:
+ byte |= SDLA_S508_SEG_D;
+ break;
+ case 0x0E:
+ byte |= SDLA_S508_SEG_E;
+ break;
+ }
+ break;
+ }
+
+ /* set the memory bits, and enable access */
+ outb(byte, base + SDLA_REG_PC_WINDOW);
+
+ switch(flp->type)
+ {
+ case SDLA_S502E:
+ flp->state = SDLA_S502E_ENABLE;
+ break;
+ case SDLA_S507:
+ flp->state |= SDLA_MEMEN;
+ break;
+ case SDLA_S508:
+ flp->state = SDLA_MEMEN;
+ break;
+ }
+ outb(flp->state, base + SDLA_REG_CONTROL);
+
+ dev->irq = map->irq;
+ dev->base_addr = base;
+ dev->mem_start = map->mem_start;
+ dev->mem_end = dev->mem_start + 0x2000;
+ flp->initialized = 1;
+ return 0;
+
+fail2:
+ free_irq(map->irq, dev);
+fail:
+ release_region(base, SDLA_IO_EXTENTS);
+ return err;
+}
+
+static struct net_device_stats *sdla_stats(struct net_device *dev)
+{
+ struct frad_local *flp;
+ flp = dev->priv;
+
+ return(&flp->stats);
+}
+
+static void setup_sdla(struct net_device *dev)
+{
+ struct frad_local *flp = dev->priv;
+
+ netdev_boot_setup_check(dev);
+
+ SET_MODULE_OWNER(dev);
+ dev->flags = 0;
+ dev->type = 0xFFFF;
+ dev->hard_header_len = 0;
+ dev->addr_len = 0;
+ dev->mtu = SDLA_MAX_MTU;
+
+ dev->open = sdla_open;
+ dev->stop = sdla_close;
+ dev->do_ioctl = sdla_ioctl;
+ dev->set_config = sdla_set_config;
+ dev->get_stats = sdla_stats;
+ dev->hard_start_xmit = sdla_transmit;
+ dev->change_mtu = sdla_change_mtu;
+
+ flp->activate = sdla_activate;
+ flp->deactivate = sdla_deactivate;
+ flp->assoc = sdla_assoc;
+ flp->deassoc = sdla_deassoc;
+ flp->dlci_conf = sdla_dlci_conf;
+
+ init_timer(&flp->timer);
+ flp->timer.expires = 1;
+ flp->timer.data = (unsigned long) dev;
+ flp->timer.function = sdla_poll;
+}
+
+static struct net_device *sdla;
+
+static int __init init_sdla(void)
+{
+ int err;
+
+ printk("%s.\n", version);
+
+ sdla = alloc_netdev(sizeof(struct frad_local), "sdla0", setup_sdla);
+ if (!sdla)
+ return -ENOMEM;
+
+ err = register_netdev(sdla);
+ if (err)
+ free_netdev(sdla);
+
+ return err;
+}
+
+static void __exit exit_sdla(void)
+{
+ struct frad_local *flp = sdla->priv;
+
+ unregister_netdev(sdla);
+ if (flp->initialized) {
+ free_irq(sdla->irq, sdla);
+ release_region(sdla->base_addr, SDLA_IO_EXTENTS);
+ }
+ del_timer_sync(&flp->timer);
+ free_netdev(sdla);
+}
+
+MODULE_LICENSE("GPL");
+
+module_init(init_sdla);
+module_exit(exit_sdla);
diff --git a/drivers/net/wan/sdla_chdlc.c b/drivers/net/wan/sdla_chdlc.c
new file mode 100644
index 00000000000..afbe0024e3e
--- /dev/null
+++ b/drivers/net/wan/sdla_chdlc.c
@@ -0,0 +1,4433 @@
+/*****************************************************************************
+* sdla_chdlc.c WANPIPE(tm) Multiprotocol WAN Link Driver. Cisco HDLC module.
+*
+* Authors: Nenad Corbic <ncorbic@sangoma.com>
+* Gideon Hack
+*
+* Copyright: (c) 1995-2001 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Feb 28, 2001 Nenad Corbic Updated if_tx_timeout() routine for
+* 2.4.X kernels.
+* Jan 25, 2001 Nenad Corbic Added a TTY Sync serial driver over the
+* HDLC streaming protocol
+* Added a TTY Async serial driver over the
+* Async protocol.
+* Dec 15, 2000 Nenad Corbic Updated for 2.4.X Kernel support
+* Nov 13, 2000 Nenad Corbic Added true interface type encoding option.
+* Tcpdump doesn't support CHDLC inteface
+* types, to fix this "true type" option will set
+* the interface type to RAW IP mode.
+* Nov 07, 2000 Nenad Corbic Added security features for UDP debugging:
+* Deny all and specify allowed requests.
+* Jun 20, 2000 Nenad Corbic Fixed the API IP ERROR bug. Caused by the
+* latest update.
+* May 09, 2000 Nenad Corbic Option to bring down an interface
+* upon disconnect.
+* Mar 23, 2000 Nenad Corbic Improved task queue, bh handling.
+* Mar 16, 2000 Nenad Corbic Fixed the SLARP Dynamic IP addressing.
+* Mar 06, 2000 Nenad Corbic Bug Fix: corrupted mbox recovery.
+* Feb 10, 2000 Gideon Hack Added ASYNC support.
+* Feb 09, 2000 Nenad Corbic Fixed two shutdown bugs in update() and
+* if_stats() functions.
+* Jan 24, 2000 Nenad Corbic Fixed a startup wanpipe state racing,
+* condition between if_open and isr.
+* Jan 10, 2000 Nenad Corbic Added new socket API support.
+* Dev 15, 1999 Nenad Corbic Fixed up header files for 2.0.X kernels
+* Nov 20, 1999 Nenad Corbic Fixed zero length API bug.
+* Sep 30, 1999 Nenad Corbic Fixed dynamic IP and route setup.
+* Sep 23, 1999 Nenad Corbic Added SMP support, fixed tracing
+* Sep 13, 1999 Nenad Corbic Split up Port 0 and 1 into separate devices.
+* Jun 02, 1999 Gideon Hack Added support for the S514 adapter.
+* Oct 30, 1998 Jaspreet Singh Added Support for CHDLC API (HDLC STREAMING).
+* Oct 28, 1998 Jaspreet Singh Added Support for Dual Port CHDLC.
+* Aug 07, 1998 David Fong Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+#include <linux/if_arp.h> /* ARPHRD_* defines */
+
+
+#include <asm/uaccess.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+#include <linux/in.h> /* sockaddr_in */
+#include <linux/inet.h>
+#include <linux/if.h>
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <linux/sdlapci.h>
+#include <asm/io.h>
+
+#include <linux/sdla_chdlc.h> /* CHDLC firmware API definitions */
+#include <linux/sdla_asy.h> /* CHDLC (async) API definitions */
+
+#include <linux/if_wanpipe_common.h> /* Socket Driver common area */
+#include <linux/if_wanpipe.h>
+
+/* TTY Includes */
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+
+
+/****** Defines & Macros ****************************************************/
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP 0x01
+#define TMR_INT_ENABLED_UPDATE 0x02
+#define TMR_INT_ENABLED_CONFIG 0x10
+
+#define MAX_IP_ERRORS 10
+
+#define TTY_CHDLC_MAX_MTU 2000
+#define CHDLC_DFLT_DATA_LEN 1500 /* default MTU */
+#define CHDLC_HDR_LEN 1
+
+#define CHDLC_API 0x01
+
+#define PORT(x) (x == 0 ? "PRIMARY" : "SECONDARY" )
+#define MAX_BH_BUFF 10
+
+//#define PRINT_DEBUG
+#ifdef PRINT_DEBUG
+#define dbg_printk(format, a...) printk(format, ## a)
+#else
+#define dbg_printk(format, a...)
+#endif
+
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following
+ * structure will incorporate the card structure along with CHDLC specific data
+ */
+
+typedef struct chdlc_private_area
+{
+ wanpipe_common_t common;
+ sdla_t *card;
+ int TracingEnabled; /* For enabling Tracing */
+ unsigned long curr_trace_addr; /* Used for Tracing */
+ unsigned long start_trace_addr;
+ unsigned long end_trace_addr;
+ unsigned long base_addr_trace_buffer;
+ unsigned long end_addr_trace_buffer;
+ unsigned short number_trace_elements;
+ unsigned available_buffer_space;
+ unsigned long router_start_time;
+ unsigned char route_status;
+ unsigned char route_removed;
+ unsigned long tick_counter; /* For 5s timeout counter */
+ unsigned long router_up_time;
+ u32 IP_address; /* IP addressing */
+ u32 IP_netmask;
+ u32 ip_local;
+ u32 ip_remote;
+ u32 ip_local_tmp;
+ u32 ip_remote_tmp;
+ u8 ip_error;
+ u8 config_chdlc;
+ u8 config_chdlc_timeout;
+ unsigned char mc; /* Mulitcast support on/off */
+ unsigned short udp_pkt_lgth; /* udp packet processing */
+ char udp_pkt_src;
+ char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+ unsigned short timer_int_enabled;
+ char update_comms_stats; /* updating comms stats */
+
+ bh_data_t *bh_head; /* Circular buffer for chdlc_bh */
+ unsigned long tq_working;
+ volatile int bh_write;
+ volatile int bh_read;
+ atomic_t bh_buff_used;
+
+ unsigned char interface_down;
+
+ /* Polling work queue entry. Each interface
+ * has its own work queue entry, which is used
+ * to defer events from the interrupt */
+ struct work_struct poll_work;
+ struct timer_list poll_delay_timer;
+
+ u8 gateway;
+ u8 true_if_encoding;
+ //FIXME: add driver stats as per frame relay!
+
+} chdlc_private_area_t;
+
+/* Route Status options */
+#define NO_ROUTE 0x00
+#define ADD_ROUTE 0x01
+#define ROUTE_ADDED 0x02
+#define REMOVE_ROUTE 0x03
+
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+/* variable for tracking how many interfaces to open for WANPIPE on the
+ two ports */
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/****** Function Prototypes *************************************************/
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device* wandev);
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+ wanif_conf_t* conf);
+
+/* Network device interface */
+static int if_init(struct net_device* dev);
+static int if_open(struct net_device* dev);
+static int if_close(struct net_device* dev);
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+ unsigned short type, void* daddr, void* saddr,
+ unsigned len);
+
+static int if_rebuild_hdr (struct sk_buff *skb);
+static struct net_device_stats* if_stats(struct net_device* dev);
+
+static int if_send(struct sk_buff* skb, struct net_device* dev);
+
+/* CHDLC Firmware interface functions */
+static int chdlc_configure (sdla_t* card, void* data);
+static int chdlc_comm_enable (sdla_t* card);
+static int chdlc_read_version (sdla_t* card, char* str);
+static int chdlc_set_intr_mode (sdla_t* card, unsigned mode);
+static int chdlc_send (sdla_t* card, void* data, unsigned len);
+static int chdlc_read_comm_err_stats (sdla_t* card);
+static int chdlc_read_op_stats (sdla_t* card);
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb);
+
+
+static int chdlc_disable_comm_shutdown (sdla_t *card);
+static void if_tx_timeout(struct net_device *dev);
+
+/* Miscellaneous CHDLC Functions */
+static int set_chdlc_config (sdla_t* card);
+static void init_chdlc_tx_rx_buff( sdla_t* card);
+static int process_chdlc_exception(sdla_t *card);
+static int process_global_exception(sdla_t *card);
+static int update_comms_stats(sdla_t* card,
+ chdlc_private_area_t* chdlc_priv_area);
+static int configure_ip (sdla_t* card);
+static int unconfigure_ip (sdla_t* card);
+static void process_route(sdla_t *card);
+static void port_set_state (sdla_t *card, int);
+static int config_chdlc (sdla_t *card);
+static void disable_comm (sdla_t *card);
+
+static void trigger_chdlc_poll(struct net_device *dev);
+static void chdlc_poll(struct net_device *dev);
+static void chdlc_poll_delay (unsigned long dev_ptr);
+
+
+/* Miscellaneous asynchronous interface Functions */
+static int set_asy_config (sdla_t* card);
+static int asy_comm_enable (sdla_t* card);
+
+/* Interrupt handlers */
+static void wpc_isr (sdla_t* card);
+static void rx_intr (sdla_t* card);
+static void timer_intr(sdla_t *);
+
+/* Bottom half handlers */
+static void chdlc_work(struct net_device *dev);
+static int chdlc_work_cleanup(struct net_device *dev);
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb);
+
+/* Miscellaneous functions */
+static int chk_bcast_mcast_addr(sdla_t* card, struct net_device* dev,
+ struct sk_buff *skb);
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+static int intr_test( sdla_t* card);
+static int udp_pkt_type( struct sk_buff *skb , sdla_t* card);
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area);
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area);
+static unsigned short calc_checksum (char *, int);
+static void s508_lock (sdla_t *card, unsigned long *smp_flags);
+static void s508_unlock (sdla_t *card, unsigned long *smp_flags);
+
+
+static int Intr_test_counter;
+
+/* TTY Global Definitions */
+
+#define NR_PORTS 4
+#define WAN_TTY_MAJOR 226
+#define WAN_TTY_MINOR 0
+
+#define WAN_CARD(port) (tty_card_map[port])
+#define MIN_PORT 0
+#define MAX_PORT NR_PORTS-1
+
+#define CRC_LENGTH 2
+
+static int wanpipe_tty_init(sdla_t *card);
+static void wanpipe_tty_receive(sdla_t *, unsigned, unsigned int);
+static void wanpipe_tty_trigger_poll(sdla_t *card);
+
+static struct tty_driver serial_driver;
+static int tty_init_cnt=0;
+
+static struct serial_state rs_table[NR_PORTS];
+
+static char tty_driver_mode=WANOPT_TTY_SYNC;
+
+static char *opt_decode[] = {"NONE","CRTSCTS","XONXOFF-RX",
+ "CRTSCTS XONXOFF-RX","XONXOFF-TX",
+ "CRTSCTS XONXOFF-TX","CRTSCTS XONXOFF"};
+static char *p_decode[] = {"NONE","ODD","EVEN"};
+
+static void* tty_card_map[NR_PORTS] = {NULL,NULL,NULL,NULL};
+
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Cisco HDLC protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup. At this
+ * point adapter is completely initialized and firmware is running.
+ * o read firmware version (to make sure it's alive)
+ * o configure adapter
+ * o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return: 0 o.k.
+ * < 0 failure.
+ */
+int wpc_init (sdla_t* card, wandev_conf_t* conf)
+{
+ unsigned char port_num;
+ int err;
+ unsigned long max_permitted_baud = 0;
+ SHARED_MEMORY_INFO_STRUCT *flags;
+
+ union
+ {
+ char str[80];
+ } u;
+ volatile CHDLC_MAILBOX_STRUCT* mb;
+ CHDLC_MAILBOX_STRUCT* mb1;
+ unsigned long timeout;
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_CHDLC) {
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id);
+ return -EINVAL;
+ }
+
+ /* Find out which Port to use */
+ if ((conf->comm_port == WANOPT_PRI) || (conf->comm_port == WANOPT_SEC)){
+ if (card->next){
+
+ if (conf->comm_port != card->next->u.c.comm_port){
+ card->u.c.comm_port = conf->comm_port;
+ }else{
+ printk(KERN_INFO "%s: ERROR - %s port used!\n",
+ card->wandev.name, PORT(conf->comm_port));
+ return -EINVAL;
+ }
+ }else{
+ card->u.c.comm_port = conf->comm_port;
+ }
+ }else{
+ printk(KERN_INFO "%s: ERROR - Invalid Port Selected!\n",
+ card->wandev.name);
+ return -EINVAL;
+ }
+
+
+ /* Initialize protocol-specific fields */
+ if(card->hw.type != SDLA_S514){
+
+ if (card->u.c.comm_port == WANOPT_PRI){
+ card->mbox = (void *) card->hw.dpmbase;
+ }else{
+ card->mbox = (void *) card->hw.dpmbase +
+ SEC_BASE_ADDR_MB_STRUCT - PRI_BASE_ADDR_MB_STRUCT;
+ }
+ }else{
+ /* for a S514 adapter, set a pointer to the actual mailbox in the */
+ /* allocated virtual memory area */
+ if (card->u.c.comm_port == WANOPT_PRI){
+ card->mbox = (void *) card->hw.dpmbase + PRI_BASE_ADDR_MB_STRUCT;
+ }else{
+ card->mbox = (void *) card->hw.dpmbase + SEC_BASE_ADDR_MB_STRUCT;
+ }
+ }
+
+ mb = mb1 = card->mbox;
+
+ if (!card->configured){
+
+ /* The board will place an 'I' in the return code to indicate that it is
+ ready to accept commands. We expect this to be completed in less
+ than 1 second. */
+
+ timeout = jiffies;
+ while (mb->return_code != 'I') /* Wait 1s for board to initialize */
+ if ((jiffies - timeout) > 1*HZ) break;
+
+ if (mb->return_code != 'I') {
+ printk(KERN_INFO
+ "%s: Initialization not completed by adapter\n",
+ card->devname);
+ printk(KERN_INFO "Please contact Sangoma representative.\n");
+ return -EIO;
+ }
+ }
+
+ /* Read firmware version. Note that when adapter initializes, it
+ * clears the mailbox, so it may appear that the first command was
+ * executed successfully when in fact it was merely erased. To work
+ * around this, we execute the first command twice.
+ */
+
+ if (chdlc_read_version(card, u.str))
+ return -EIO;
+
+ printk(KERN_INFO "%s: Running Cisco HDLC firmware v%s\n",
+ card->devname, u.str);
+
+ card->isr = &wpc_isr;
+ card->poll = NULL;
+ card->exec = NULL;
+ card->wandev.update = &update;
+ card->wandev.new_if = &new_if;
+ card->wandev.del_if = NULL;
+ card->wandev.udp_port = conf->udp_port;
+ card->disable_comm = &disable_comm;
+ card->wandev.new_if_cnt = 0;
+
+ /* reset the number of times the 'update()' proc has been called */
+ card->u.c.update_call_count = 0;
+
+ card->wandev.ttl = conf->ttl;
+ card->wandev.interface = conf->interface;
+
+ if ((card->u.c.comm_port == WANOPT_SEC && conf->interface == WANOPT_V35)&&
+ card->hw.type != SDLA_S514){
+ printk(KERN_INFO "%s: ERROR - V35 Interface not supported on S508 %s port \n",
+ card->devname, PORT(card->u.c.comm_port));
+ return -EIO;
+ }
+
+ card->wandev.clocking = conf->clocking;
+
+ port_num = card->u.c.comm_port;
+
+ /* in API mode, we can configure for "receive only" buffering */
+ if(card->hw.type == SDLA_S514) {
+ card->u.c.receive_only = conf->receive_only;
+ if(conf->receive_only) {
+ printk(KERN_INFO
+ "%s: Configured for 'receive only' mode\n",
+ card->devname);
+ }
+ }
+
+ /* Setup Port Bps */
+
+ if(card->wandev.clocking) {
+ if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+ /* For Primary Port 0 */
+ max_permitted_baud =
+ (card->hw.type == SDLA_S514) ?
+ PRI_MAX_BAUD_RATE_S514 :
+ PRI_MAX_BAUD_RATE_S508;
+
+ }else if(port_num == WANOPT_SEC) {
+ /* For Secondary Port 1 */
+ max_permitted_baud =
+ (card->hw.type == SDLA_S514) ?
+ SEC_MAX_BAUD_RATE_S514 :
+ SEC_MAX_BAUD_RATE_S508;
+ }
+
+ if(conf->bps > max_permitted_baud) {
+ conf->bps = max_permitted_baud;
+ printk(KERN_INFO "%s: Baud too high!\n",
+ card->wandev.name);
+ printk(KERN_INFO "%s: Baud rate set to %lu bps\n",
+ card->wandev.name, max_permitted_baud);
+ }
+ card->wandev.bps = conf->bps;
+ }else{
+ card->wandev.bps = 0;
+ }
+
+ /* Setup the Port MTU */
+ if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+
+ /* For Primary Port 0 */
+ card->wandev.mtu =
+ (conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+ min_t(unsigned int, conf->mtu, PRI_MAX_NO_DATA_BYTES_IN_FRAME) :
+ CHDLC_DFLT_DATA_LEN;
+ } else if(port_num == WANOPT_SEC) {
+ /* For Secondary Port 1 */
+ card->wandev.mtu =
+ (conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+ min_t(unsigned int, conf->mtu, SEC_MAX_NO_DATA_BYTES_IN_FRAME) :
+ CHDLC_DFLT_DATA_LEN;
+ }
+
+ /* Set up the interrupt status area */
+ /* Read the CHDLC Configuration and obtain:
+ * Ptr to shared memory infor struct
+ * Use this pointer to calculate the value of card->u.c.flags !
+ */
+ mb1->buffer_length = 0;
+ mb1->command = READ_CHDLC_CONFIGURATION;
+ err = sdla_exec(mb1) ? mb1->return_code : CMD_TIMEOUT;
+ if(err != COMMAND_OK) {
+ if(card->hw.type != SDLA_S514)
+ enable_irq(card->hw.irq);
+
+ chdlc_error(card, err, mb1);
+ return -EIO;
+ }
+
+ if(card->hw.type == SDLA_S514){
+ card->u.c.flags = (void *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+ ptr_shared_mem_info_struct));
+ }else{
+ card->u.c.flags = (void *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+ ptr_shared_mem_info_struct % SDLA_WINDOWSIZE));
+ }
+
+ flags = card->u.c.flags;
+
+ /* This is for the ports link state */
+ card->wandev.state = WAN_DUALPORT;
+ card->u.c.state = WAN_DISCONNECTED;
+
+
+ if (!card->wandev.piggyback){
+ int err;
+
+ /* Perform interrupt testing */
+ err = intr_test(card);
+
+ if(err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) {
+ printk(KERN_INFO "%s: Interrupt test failed (%i)\n",
+ card->devname, Intr_test_counter);
+ printk(KERN_INFO "%s: Please choose another interrupt\n",
+ card->devname);
+ return -EIO;
+ }
+
+ printk(KERN_INFO "%s: Interrupt test passed (%i)\n",
+ card->devname, Intr_test_counter);
+ card->configured = 1;
+ }
+
+ if ((card->tty_opt=conf->tty) == WANOPT_YES){
+ int err;
+ card->tty_minor = conf->tty_minor;
+
+ /* On ASYNC connections internal clocking
+ * is mandatory */
+ if ((card->u.c.async_mode = conf->tty_mode)){
+ card->wandev.clocking = 1;
+ }
+ err=wanpipe_tty_init(card);
+ if (err){
+ return err;
+ }
+ }else{
+
+
+ if (chdlc_set_intr_mode(card, APP_INT_ON_TIMER)){
+ printk (KERN_INFO "%s: "
+ "Failed to set interrupt triggers!\n",
+ card->devname);
+ return -EIO;
+ }
+
+ /* Mask the Timer interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TIMER;
+ }
+
+ /* If we are using CHDLC in backup mode, this flag will
+ * indicate not to look for IP addresses in config_chdlc()*/
+ card->u.c.backup = conf->backup;
+
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics
+ * This procedure is called when updating the PROC file system and returns
+ * various communications statistics. These statistics are accumulated from 3
+ * different locations:
+ * 1) The 'if_stats' recorded for the device.
+ * 2) Communication error statistics on the adapter.
+ * 3) CHDLC operational statistics on the adapter.
+ * The board level statistics are read during a timer interrupt. Note that we
+ * read the error and operational statistics during consecitive timer ticks so
+ * as to minimize the time that we are inside the interrupt handler.
+ *
+ */
+static int update(struct wan_device* wandev)
+{
+ sdla_t* card = wandev->private;
+ struct net_device* dev;
+ volatile chdlc_private_area_t* chdlc_priv_area;
+ SHARED_MEMORY_INFO_STRUCT *flags;
+ unsigned long timeout;
+
+ /* sanity checks */
+ if((wandev == NULL) || (wandev->private == NULL))
+ return -EFAULT;
+
+ if(wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ /* more sanity checks */
+ if(!card->u.c.flags)
+ return -ENODEV;
+
+ if(test_bit(PERI_CRIT, (void*)&card->wandev.critical))
+ return -EAGAIN;
+
+ if((dev=card->wandev.dev) == NULL)
+ return -ENODEV;
+
+ if((chdlc_priv_area=dev->priv) == NULL)
+ return -ENODEV;
+
+ flags = card->u.c.flags;
+ if(chdlc_priv_area->update_comms_stats){
+ return -EAGAIN;
+ }
+
+ /* we will need 2 timer interrupts to complete the */
+ /* reading of the statistics */
+ chdlc_priv_area->update_comms_stats = 2;
+ flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+ chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UPDATE;
+
+ /* wait a maximum of 1 second for the statistics to be updated */
+ timeout = jiffies;
+ for(;;) {
+ if(chdlc_priv_area->update_comms_stats == 0)
+ break;
+ if ((jiffies - timeout) > (1 * HZ)){
+ chdlc_priv_area->update_comms_stats = 0;
+ chdlc_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_UPDATE;
+ return -EAGAIN;
+ }
+ }
+
+ return 0;
+}
+
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return: 0 o.k.
+ * < 0 failure (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+ wanif_conf_t* conf)
+{
+ sdla_t* card = wandev->private;
+ chdlc_private_area_t* chdlc_priv_area;
+
+
+ printk(KERN_INFO "%s: Configuring Interface: %s\n",
+ card->devname, conf->name);
+
+ if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+ printk(KERN_INFO "%s: Invalid interface name!\n",
+ card->devname);
+ return -EINVAL;
+ }
+
+ /* allocate and initialize private data */
+ chdlc_priv_area = kmalloc(sizeof(chdlc_private_area_t), GFP_KERNEL);
+
+ if(chdlc_priv_area == NULL)
+ return -ENOMEM;
+
+ memset(chdlc_priv_area, 0, sizeof(chdlc_private_area_t));
+
+ chdlc_priv_area->card = card;
+ chdlc_priv_area->common.sk = NULL;
+ chdlc_priv_area->common.func = NULL;
+
+ /* initialize data */
+ strcpy(card->u.c.if_name, conf->name);
+
+ if(card->wandev.new_if_cnt > 0) {
+ kfree(chdlc_priv_area);
+ return -EEXIST;
+ }
+
+ card->wandev.new_if_cnt++;
+
+ chdlc_priv_area->TracingEnabled = 0;
+ chdlc_priv_area->route_status = NO_ROUTE;
+ chdlc_priv_area->route_removed = 0;
+
+ card->u.c.async_mode = conf->async_mode;
+
+ /* setup for asynchronous mode */
+ if(conf->async_mode) {
+ printk(KERN_INFO "%s: Configuring for asynchronous mode\n",
+ wandev->name);
+
+ if(card->u.c.comm_port == WANOPT_PRI) {
+ printk(KERN_INFO
+ "%s:Asynchronous mode on secondary port only\n",
+ wandev->name);
+ kfree(chdlc_priv_area);
+ return -EINVAL;
+ }
+
+ if(strcmp(conf->usedby, "WANPIPE") == 0) {
+ printk(KERN_INFO
+ "%s: Running in WANIPE Async Mode\n", wandev->name);
+ card->u.c.usedby = WANPIPE;
+ }else{
+ card->u.c.usedby = API;
+ }
+
+ if(!card->wandev.clocking) {
+ printk(KERN_INFO
+ "%s: Asynch. clocking must be 'Internal'\n",
+ wandev->name);
+ kfree(chdlc_priv_area);
+ return -EINVAL;
+ }
+
+ if((card->wandev.bps < MIN_ASY_BAUD_RATE) ||
+ (card->wandev.bps > MAX_ASY_BAUD_RATE)) {
+ printk(KERN_INFO "%s: Selected baud rate is invalid.\n",
+ wandev->name);
+ printk(KERN_INFO "Must be between %u and %u bps.\n",
+ MIN_ASY_BAUD_RATE, MAX_ASY_BAUD_RATE);
+ kfree(chdlc_priv_area);
+ return -EINVAL;
+ }
+
+ card->u.c.api_options = 0;
+ if (conf->asy_data_trans == WANOPT_YES) {
+ card->u.c.api_options |= ASY_RX_DATA_TRANSPARENT;
+ }
+
+ card->u.c.protocol_options = 0;
+ if (conf->rts_hs_for_receive == WANOPT_YES) {
+ card->u.c.protocol_options |= ASY_RTS_HS_FOR_RX;
+ }
+ if (conf->xon_xoff_hs_for_receive == WANOPT_YES) {
+ card->u.c.protocol_options |= ASY_XON_XOFF_HS_FOR_RX;
+ }
+ if (conf->xon_xoff_hs_for_transmit == WANOPT_YES) {
+ card->u.c.protocol_options |= ASY_XON_XOFF_HS_FOR_TX;
+ }
+ if (conf->dcd_hs_for_transmit == WANOPT_YES) {
+ card->u.c.protocol_options |= ASY_DCD_HS_FOR_TX;
+ }
+ if (conf->cts_hs_for_transmit == WANOPT_YES) {
+ card->u.c.protocol_options |= ASY_CTS_HS_FOR_TX;
+ }
+
+ card->u.c.tx_bits_per_char = conf->tx_bits_per_char;
+ card->u.c.rx_bits_per_char = conf->rx_bits_per_char;
+ card->u.c.stop_bits = conf->stop_bits;
+ card->u.c.parity = conf->parity;
+ card->u.c.break_timer = conf->break_timer;
+ card->u.c.inter_char_timer = conf->inter_char_timer;
+ card->u.c.rx_complete_length = conf->rx_complete_length;
+ card->u.c.xon_char = conf->xon_char;
+
+ } else { /* setup for synchronous mode */
+
+ card->u.c.protocol_options = 0;
+ if (conf->ignore_dcd == WANOPT_YES){
+ card->u.c.protocol_options |= IGNORE_DCD_FOR_LINK_STAT;
+ }
+ if (conf->ignore_cts == WANOPT_YES){
+ card->u.c.protocol_options |= IGNORE_CTS_FOR_LINK_STAT;
+ }
+
+ if (conf->ignore_keepalive == WANOPT_YES) {
+ card->u.c.protocol_options |=
+ IGNORE_KPALV_FOR_LINK_STAT;
+ card->u.c.kpalv_tx = MIN_Tx_KPALV_TIMER;
+ card->u.c.kpalv_rx = MIN_Rx_KPALV_TIMER;
+ card->u.c.kpalv_err = MIN_KPALV_ERR_TOL;
+
+ } else { /* Do not ignore keepalives */
+ card->u.c.kpalv_tx =
+ ((conf->keepalive_tx_tmr - MIN_Tx_KPALV_TIMER)
+ >= 0) ?
+ min_t(unsigned int, conf->keepalive_tx_tmr,MAX_Tx_KPALV_TIMER) :
+ DEFAULT_Tx_KPALV_TIMER;
+
+ card->u.c.kpalv_rx =
+ ((conf->keepalive_rx_tmr - MIN_Rx_KPALV_TIMER)
+ >= 0) ?
+ min_t(unsigned int, conf->keepalive_rx_tmr,MAX_Rx_KPALV_TIMER) :
+ DEFAULT_Rx_KPALV_TIMER;
+
+ card->u.c.kpalv_err =
+ ((conf->keepalive_err_margin-MIN_KPALV_ERR_TOL)
+ >= 0) ?
+ min_t(unsigned int, conf->keepalive_err_margin,
+ MAX_KPALV_ERR_TOL) :
+ DEFAULT_KPALV_ERR_TOL;
+ }
+
+ /* Setup slarp timer to control delay between slarps */
+ card->u.c.slarp_timer =
+ ((conf->slarp_timer - MIN_SLARP_REQ_TIMER) >= 0) ?
+ min_t(unsigned int, conf->slarp_timer, MAX_SLARP_REQ_TIMER) :
+ DEFAULT_SLARP_REQ_TIMER;
+
+ if (conf->hdlc_streaming == WANOPT_YES) {
+ printk(KERN_INFO "%s: Enabling HDLC STREAMING Mode\n",
+ wandev->name);
+ card->u.c.protocol_options = HDLC_STREAMING_MODE;
+ }
+
+ if ((chdlc_priv_area->true_if_encoding = conf->true_if_encoding) == WANOPT_YES){
+ printk(KERN_INFO
+ "%s: Enabling, true interface type encoding.\n",
+ card->devname);
+ }
+
+ /* Setup wanpipe as a router (WANPIPE) or as an API */
+ if( strcmp(conf->usedby, "WANPIPE") == 0) {
+
+ printk(KERN_INFO "%s: Running in WANPIPE mode!\n",
+ wandev->name);
+ card->u.c.usedby = WANPIPE;
+
+ /* Option to bring down the interface when
+ * the link goes down */
+ if (conf->if_down){
+ set_bit(DYN_OPT_ON,&chdlc_priv_area->interface_down);
+ printk(KERN_INFO
+ "%s: Dynamic interface configuration enabled\n",
+ card->devname);
+ }
+
+ } else if( strcmp(conf->usedby, "API") == 0) {
+ card->u.c.usedby = API;
+ printk(KERN_INFO "%s: Running in API mode !\n",
+ wandev->name);
+ }
+ }
+
+ /* Tells us that if this interface is a
+ * gateway or not */
+ if ((chdlc_priv_area->gateway = conf->gateway) == WANOPT_YES){
+ printk(KERN_INFO "%s: Interface %s is set as a gateway.\n",
+ card->devname,card->u.c.if_name);
+ }
+
+ /* Get Multicast Information */
+ chdlc_priv_area->mc = conf->mc;
+
+ /* prepare network device data space for registration */
+ strcpy(dev->name,card->u.c.if_name);
+
+ dev->init = &if_init;
+ dev->priv = chdlc_priv_area;
+
+ /* Initialize the polling work routine */
+ INIT_WORK(&chdlc_priv_area->poll_work, (void*)(void*)chdlc_poll, dev);
+
+ /* Initialize the polling delay timer */
+ init_timer(&chdlc_priv_area->poll_delay_timer);
+ chdlc_priv_area->poll_delay_timer.data = (unsigned long)dev;
+ chdlc_priv_area->poll_delay_timer.function = chdlc_poll_delay;
+
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration. Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device* dev)
+{
+ chdlc_private_area_t* chdlc_priv_area = dev->priv;
+ sdla_t* card = chdlc_priv_area->card;
+ struct wan_device* wandev = &card->wandev;
+
+ /* Initialize device driver entry points */
+ dev->open = &if_open;
+ dev->stop = &if_close;
+ dev->hard_header = &if_header;
+ dev->rebuild_header = &if_rebuild_hdr;
+ dev->hard_start_xmit = &if_send;
+ dev->get_stats = &if_stats;
+ dev->tx_timeout = &if_tx_timeout;
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+ /* Initialize media-specific parameters */
+ dev->flags |= IFF_POINTOPOINT;
+ dev->flags |= IFF_NOARP;
+
+ /* Enable Mulitcasting if user selected */
+ if (chdlc_priv_area->mc == WANOPT_YES){
+ dev->flags |= IFF_MULTICAST;
+ }
+
+ if (chdlc_priv_area->true_if_encoding){
+ dev->type = ARPHRD_HDLC; /* This breaks the tcpdump */
+ }else{
+ dev->type = ARPHRD_PPP;
+ }
+
+ dev->mtu = card->wandev.mtu;
+ /* for API usage, add the API header size to the requested MTU size */
+ if(card->u.c.usedby == API) {
+ dev->mtu += sizeof(api_tx_hdr_t);
+ }
+
+ dev->hard_header_len = CHDLC_HDR_LEN;
+
+ /* Initialize hardware parameters */
+ dev->irq = wandev->irq;
+ dev->dma = wandev->dma;
+ dev->base_addr = wandev->ioport;
+ dev->mem_start = wandev->maddr;
+ dev->mem_end = wandev->maddr + wandev->msize - 1;
+
+ /* Set transmit buffer queue length
+ * If too low packets will not be retransmitted
+ * by stack.
+ */
+ dev->tx_queue_len = 100;
+ SET_MODULE_OWNER(dev);
+
+ return 0;
+}
+
+/*============================================================================
+ * Open network interface.
+ * o enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device* dev)
+{
+ chdlc_private_area_t* chdlc_priv_area = dev->priv;
+ sdla_t* card = chdlc_priv_area->card;
+ struct timeval tv;
+ int err = 0;
+
+ /* Only one open per interface is allowed */
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ /* Initialize the work queue entry */
+ chdlc_priv_area->tq_working=0;
+
+ INIT_WORK(&chdlc_priv_area->common.wanpipe_work,
+ (void *)(void *)chdlc_work, dev);
+
+ /* Allocate and initialize BH circular buffer */
+ /* Add 1 to MAX_BH_BUFF so we don't have test with (MAX_BH_BUFF-1) */
+ chdlc_priv_area->bh_head = kmalloc((sizeof(bh_data_t)*(MAX_BH_BUFF+1)),GFP_ATOMIC);
+ memset(chdlc_priv_area->bh_head,0,(sizeof(bh_data_t)*(MAX_BH_BUFF+1)));
+ atomic_set(&chdlc_priv_area->bh_buff_used, 0);
+
+ do_gettimeofday(&tv);
+ chdlc_priv_area->router_start_time = tv.tv_sec;
+
+ netif_start_queue(dev);
+
+ wanpipe_open(card);
+
+ /* TTY is configured during wanpipe_set_termios
+ * call, not here */
+ if (card->tty_opt)
+ return err;
+
+ set_bit(0,&chdlc_priv_area->config_chdlc);
+ chdlc_priv_area->config_chdlc_timeout=jiffies;
+
+ /* Start the CHDLC configuration after 1sec delay.
+ * This will give the interface initilization time
+ * to finish its configuration */
+ mod_timer(&chdlc_priv_area->poll_delay_timer, jiffies + HZ);
+ return err;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last close, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device* dev)
+{
+ chdlc_private_area_t* chdlc_priv_area = dev->priv;
+ sdla_t* card = chdlc_priv_area->card;
+
+ if (chdlc_priv_area->bh_head){
+ int i;
+ struct sk_buff *skb;
+
+ for (i=0; i<(MAX_BH_BUFF+1); i++){
+ skb = ((bh_data_t *)&chdlc_priv_area->bh_head[i])->skb;
+ if (skb != NULL){
+ dev_kfree_skb_any(skb);
+ }
+ }
+ kfree(chdlc_priv_area->bh_head);
+ chdlc_priv_area->bh_head=NULL;
+ }
+
+ netif_stop_queue(dev);
+ wanpipe_close(card);
+ del_timer(&chdlc_priv_area->poll_delay_timer);
+ return 0;
+}
+
+static void disable_comm (sdla_t *card)
+{
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+ if (card->u.c.comm_enabled){
+ chdlc_disable_comm_shutdown (card);
+ }else{
+ flags->interrupt_info_struct.interrupt_permission = 0;
+ }
+
+ if (!tty_init_cnt)
+ return;
+
+ if (card->tty_opt){
+ struct serial_state * state;
+ if (!(--tty_init_cnt)){
+ int e1;
+ serial_driver.refcount=0;
+
+ if ((e1 = tty_unregister_driver(&serial_driver)))
+ printk("SERIAL: failed to unregister serial driver (%d)\n",
+ e1);
+ printk(KERN_INFO "%s: Unregistering TTY Driver, Major %i\n",
+ card->devname,WAN_TTY_MAJOR);
+ }
+ card->tty=NULL;
+ tty_card_map[card->tty_minor]=NULL;
+ state = &rs_table[card->tty_minor];
+ memset(state, 0, sizeof(*state));
+ }
+ return;
+}
+
+
+/*============================================================================
+ * Build media header.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol' field of
+ * the socket buffer, so that we don't forget it. If packet type is not
+ * supported, set skb->protocol to 0 and discard packet later.
+ *
+ * Return: media header length.
+ */
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+ unsigned short type, void* daddr, void* saddr,
+ unsigned len)
+{
+ skb->protocol = htons(type);
+
+ return CHDLC_HDR_LEN;
+}
+
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+ chdlc_private_area_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ /* If our device stays busy for at least 5 seconds then we will
+ * kick start the device by making dev->tbusy = 0. We expect
+ * that our device never stays busy more than 5 seconds. So this
+ * is only used as a last resort.
+ */
+
+ ++card->wandev.stats.collisions;
+
+ printk (KERN_INFO "%s: Transmit timed out on %s\n", card->devname,dev->name);
+ netif_wake_queue (dev);
+}
+
+
+
+/*============================================================================
+ * Re-build media header.
+ *
+ * Return: 1 physical address resolved.
+ * 0 physical address not resolved
+ */
+static int if_rebuild_hdr (struct sk_buff *skb)
+{
+ return 1;
+}
+
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ * transmit from overlapping.
+ * o check link state. If link is not up, then drop the packet.
+ * o execute adapter send command.
+ * o free socket buffer
+ *
+ * Return: 0 complete (socket buffer must be freed)
+ * non-0 packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ * bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ * protocol stack and can be used for flow control with protocol layer.
+ */
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+ sdla_t *card = chdlc_priv_area->card;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ INTERRUPT_INFORMATION_STRUCT *chdlc_int = &flags->interrupt_info_struct;
+ int udp_type = 0;
+ unsigned long smp_flags;
+ int err=0;
+
+ netif_stop_queue(dev);
+
+ if (skb == NULL){
+ /* If we get here, some higher layer thinks we've missed an
+ * tx-done interrupt.
+ */
+ printk(KERN_INFO "%s: interface %s got kicked!\n",
+ card->devname, dev->name);
+
+ netif_wake_queue(dev);
+ return 0;
+ }
+
+ if (ntohs(skb->protocol) != htons(PVC_PROT)){
+
+ /* check the udp packet type */
+
+ udp_type = udp_pkt_type(skb, card);
+
+ if (udp_type == UDP_CPIPE_TYPE){
+ if(store_udp_mgmt_pkt(UDP_PKT_FRM_STACK, card, skb, dev,
+ chdlc_priv_area)){
+ chdlc_int->interrupt_permission |=
+ APP_INT_ON_TIMER;
+ }
+ netif_start_queue(dev);
+ return 0;
+ }
+
+ /* check to see if the source IP address is a broadcast or */
+ /* multicast IP address */
+ if(chk_bcast_mcast_addr(card, dev, skb)){
+ ++card->wandev.stats.tx_dropped;
+ dev_kfree_skb_any(skb);
+ netif_start_queue(dev);
+ return 0;
+ }
+ }
+
+ /* Lock the 508 Card: SMP is supported */
+ if(card->hw.type != SDLA_S514){
+ s508_lock(card,&smp_flags);
+ }
+
+ if(test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+
+ printk(KERN_INFO "%s: Critical in if_send: %lx\n",
+ card->wandev.name,card->wandev.critical);
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+ goto if_send_exit_crit;
+ }
+
+ if(card->u.c.state != WAN_CONNECTED){
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+
+ }else if(!skb->protocol){
+ ++card->wandev.stats.tx_errors;
+ netif_start_queue(dev);
+
+ }else {
+ void* data = skb->data;
+ unsigned len = skb->len;
+ unsigned char attr;
+
+ /* If it's an API packet pull off the API
+ * header. Also check that the packet size
+ * is larger than the API header
+ */
+ if (card->u.c.usedby == API){
+ api_tx_hdr_t* api_tx_hdr;
+
+ /* discard the frame if we are configured for */
+ /* 'receive only' mode or if there is no data */
+ if (card->u.c.receive_only ||
+ (len <= sizeof(api_tx_hdr_t))) {
+
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+ goto if_send_exit_crit;
+ }
+
+ api_tx_hdr = (api_tx_hdr_t *)data;
+ attr = api_tx_hdr->attr;
+ data += sizeof(api_tx_hdr_t);
+ len -= sizeof(api_tx_hdr_t);
+ }
+
+ if(chdlc_send(card, data, len)) {
+ netif_stop_queue(dev);
+ }else{
+ ++card->wandev.stats.tx_packets;
+ card->wandev.stats.tx_bytes += len;
+
+ netif_start_queue(dev);
+
+ dev->trans_start = jiffies;
+ }
+ }
+
+if_send_exit_crit:
+
+ if (!(err=netif_queue_stopped(dev))) {
+ dev_kfree_skb_any(skb);
+ }else{
+ chdlc_priv_area->tick_counter = jiffies;
+ chdlc_int->interrupt_permission |= APP_INT_ON_TX_FRAME;
+ }
+
+ clear_bit(SEND_CRIT, (void*)&card->wandev.critical);
+ if(card->hw.type != SDLA_S514){
+ s508_unlock(card,&smp_flags);
+ }
+
+ return err;
+}
+
+
+/*============================================================================
+ * Check to see if the packet to be transmitted contains a broadcast or
+ * multicast source IP address.
+ */
+
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+ struct sk_buff *skb)
+{
+ u32 src_ip_addr;
+ u32 broadcast_ip_addr = 0;
+ struct in_device *in_dev;
+
+ /* read the IP source address from the outgoing packet */
+ src_ip_addr = *(u32 *)(skb->data + 12);
+
+ /* read the IP broadcast address for the device */
+ in_dev = dev->ip_ptr;
+ if(in_dev != NULL) {
+ struct in_ifaddr *ifa= in_dev->ifa_list;
+ if(ifa != NULL)
+ broadcast_ip_addr = ifa->ifa_broadcast;
+ else
+ return 0;
+ }
+
+ /* check if the IP Source Address is a Broadcast address */
+ if((dev->flags & IFF_BROADCAST) && (src_ip_addr == broadcast_ip_addr)) {
+ printk(KERN_INFO "%s: Broadcast Source Address silently discarded\n",
+ card->devname);
+ return 1;
+ }
+
+ /* check if the IP Source Address is a Multicast address */
+ if((ntohl(src_ip_addr) >= 0xE0000001) &&
+ (ntohl(src_ip_addr) <= 0xFFFFFFFE)) {
+ printk(KERN_INFO "%s: Multicast Source Address silently discarded\n",
+ card->devname);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return length of reply.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+
+ unsigned short len, udp_length, temp, ip_length;
+ unsigned long ip_temp;
+ int even_bound = 0;
+ chdlc_udp_pkt_t *c_udp_pkt = (chdlc_udp_pkt_t *)data;
+
+ /* Set length of packet */
+ len = sizeof(ip_pkt_t)+
+ sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ sizeof(trace_info_t)+
+ mbox_len;
+
+ /* fill in UDP reply */
+ c_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+
+ /* fill in UDP length */
+ udp_length = sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ sizeof(trace_info_t)+
+ mbox_len;
+
+ /* put it on an even boundary */
+ if ( udp_length & 0x0001 ) {
+ udp_length += 1;
+ len += 1;
+ even_bound = 1;
+ }
+
+ temp = (udp_length<<8)|(udp_length>>8);
+ c_udp_pkt->udp_pkt.udp_length = temp;
+
+ /* swap UDP ports */
+ temp = c_udp_pkt->udp_pkt.udp_src_port;
+ c_udp_pkt->udp_pkt.udp_src_port =
+ c_udp_pkt->udp_pkt.udp_dst_port;
+ c_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+ /* add UDP pseudo header */
+ temp = 0x1100;
+ *((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound)) = temp;
+ temp = (udp_length<<8)|(udp_length>>8);
+ *((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+
+ /* calculate UDP checksum */
+ c_udp_pkt->udp_pkt.udp_checksum = 0;
+ c_udp_pkt->udp_pkt.udp_checksum = calc_checksum(&data[UDP_OFFSET],udp_length+UDP_OFFSET);
+
+ /* fill in IP length */
+ ip_length = len;
+ temp = (ip_length<<8)|(ip_length>>8);
+ c_udp_pkt->ip_pkt.total_length = temp;
+
+ /* swap IP addresses */
+ ip_temp = c_udp_pkt->ip_pkt.ip_src_address;
+ c_udp_pkt->ip_pkt.ip_src_address = c_udp_pkt->ip_pkt.ip_dst_address;
+ c_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+ /* fill in IP checksum */
+ c_udp_pkt->ip_pkt.hdr_checksum = 0;
+ c_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data,sizeof(ip_pkt_t));
+
+ return len;
+
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+ unsigned short temp;
+ unsigned long sum=0;
+ int i;
+
+ for( i = 0; i <len; i+=2 ) {
+ memcpy(&temp,&data[i],2);
+ sum += (unsigned long)temp;
+ }
+
+ while (sum >> 16 ) {
+ sum = (sum & 0xffffUL) + (sum >> 16);
+ }
+
+ temp = (unsigned short)sum;
+ temp = ~temp;
+
+ if( temp == 0 )
+ temp = 0xffff;
+
+ return temp;
+}
+
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ */
+static struct net_device_stats* if_stats(struct net_device* dev)
+{
+ sdla_t *my_card;
+ chdlc_private_area_t* chdlc_priv_area;
+
+ if ((chdlc_priv_area=dev->priv) == NULL)
+ return NULL;
+
+ my_card = chdlc_priv_area->card;
+ return &my_card->wandev.stats;
+}
+
+
+/****** Cisco HDLC Firmware Interface Functions *******************************/
+
+/*============================================================================
+ * Read firmware code version.
+ * Put code version as ASCII string in str.
+ */
+static int chdlc_read_version (sdla_t* card, char* str)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int len;
+ char err;
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if(err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ }
+ else if (str) { /* is not null */
+ len = mb->buffer_length;
+ memcpy(str, mb->data, len);
+ str[len] = '\0';
+ }
+ return (err);
+}
+
+/*-----------------------------------------------------------------------------
+ * Configure CHDLC firmware.
+ */
+static int chdlc_configure (sdla_t* card, void* data)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT *mailbox = card->mbox;
+ int data_length = sizeof(CHDLC_CONFIGURATION_STRUCT);
+
+ mailbox->buffer_length = data_length;
+ memcpy(mailbox->data, data, data_length);
+ mailbox->command = SET_CHDLC_CONFIGURATION;
+ err = sdla_exec(mailbox) ? mailbox->return_code : CMD_TIMEOUT;
+
+ if (err != COMMAND_OK) chdlc_error (card, err, mailbox);
+
+ return err;
+}
+
+
+/*============================================================================
+ * Set interrupt mode -- HDLC Version.
+ */
+
+static int chdlc_set_intr_mode (sdla_t* card, unsigned mode)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ CHDLC_INT_TRIGGERS_STRUCT* int_data =
+ (CHDLC_INT_TRIGGERS_STRUCT *)mb->data;
+ int err;
+
+ int_data->CHDLC_interrupt_triggers = mode;
+ int_data->IRQ = card->hw.irq;
+ int_data->interrupt_timer = 1;
+
+ mb->buffer_length = sizeof(CHDLC_INT_TRIGGERS_STRUCT);
+ mb->command = SET_CHDLC_INTERRUPT_TRIGGERS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error (card, err, mb);
+ return err;
+}
+
+
+/*===========================================================
+ * chdlc_disable_comm_shutdown
+ *
+ * Shutdown() disables the communications. We must
+ * have a sparate functions, because we must not
+ * call chdlc_error() hander since the private
+ * area has already been replaced */
+
+static int chdlc_disable_comm_shutdown (sdla_t *card)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ CHDLC_INT_TRIGGERS_STRUCT* int_data =
+ (CHDLC_INT_TRIGGERS_STRUCT *)mb->data;
+ int err;
+
+ /* Disable Interrutps */
+ int_data->CHDLC_interrupt_triggers = 0;
+ int_data->IRQ = card->hw.irq;
+ int_data->interrupt_timer = 1;
+
+ mb->buffer_length = sizeof(CHDLC_INT_TRIGGERS_STRUCT);
+ mb->command = SET_CHDLC_INTERRUPT_TRIGGERS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ /* Disable Communications */
+
+ if (card->u.c.async_mode) {
+ mb->command = DISABLE_ASY_COMMUNICATIONS;
+ }else{
+ mb->command = DISABLE_CHDLC_COMMUNICATIONS;
+ }
+
+ mb->buffer_length = 0;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ card->u.c.comm_enabled = 0;
+
+ return 0;
+}
+
+/*============================================================================
+ * Enable communications.
+ */
+
+static int chdlc_comm_enable (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = ENABLE_CHDLC_COMMUNICATIONS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card, err, mb);
+ else
+ card->u.c.comm_enabled = 1;
+
+ return err;
+}
+
+/*============================================================================
+ * Read communication error statistics.
+ */
+static int chdlc_read_comm_err_stats (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = READ_COMMS_ERROR_STATS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card,err,mb);
+ return err;
+}
+
+
+/*============================================================================
+ * Read CHDLC operational statistics.
+ */
+static int chdlc_read_op_stats (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_OPERATIONAL_STATS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card,err,mb);
+ return err;
+}
+
+
+/*============================================================================
+ * Update communications error and general packet statistics.
+ */
+static int update_comms_stats(sdla_t* card,
+ chdlc_private_area_t* chdlc_priv_area)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ COMMS_ERROR_STATS_STRUCT* err_stats;
+ CHDLC_OPERATIONAL_STATS_STRUCT *op_stats;
+
+ /* on the first timer interrupt, read the comms error statistics */
+ if(chdlc_priv_area->update_comms_stats == 2) {
+ if(chdlc_read_comm_err_stats(card))
+ return 1;
+ err_stats = (COMMS_ERROR_STATS_STRUCT *)mb->data;
+ card->wandev.stats.rx_over_errors =
+ err_stats->Rx_overrun_err_count;
+ card->wandev.stats.rx_crc_errors =
+ err_stats->CRC_err_count;
+ card->wandev.stats.rx_frame_errors =
+ err_stats->Rx_abort_count;
+ card->wandev.stats.rx_fifo_errors =
+ err_stats->Rx_dis_pri_bfrs_full_count;
+ card->wandev.stats.rx_missed_errors =
+ card->wandev.stats.rx_fifo_errors;
+ card->wandev.stats.tx_aborted_errors =
+ err_stats->sec_Tx_abort_count;
+ }
+
+ /* on the second timer interrupt, read the operational statistics */
+ else {
+ if(chdlc_read_op_stats(card))
+ return 1;
+ op_stats = (CHDLC_OPERATIONAL_STATS_STRUCT *)mb->data;
+ card->wandev.stats.rx_length_errors =
+ (op_stats->Rx_Data_discard_short_count +
+ op_stats->Rx_Data_discard_long_count);
+ }
+
+ return 0;
+}
+
+/*============================================================================
+ * Send packet.
+ * Return: 0 - o.k.
+ * 1 - no transmit buffers available
+ */
+static int chdlc_send (sdla_t* card, void* data, unsigned len)
+{
+ CHDLC_DATA_TX_STATUS_EL_STRUCT *txbuf = card->u.c.txbuf;
+
+ if (txbuf->opp_flag)
+ return 1;
+
+ sdla_poke(&card->hw, txbuf->ptr_data_bfr, data, len);
+
+ txbuf->frame_length = len;
+ txbuf->opp_flag = 1; /* start transmission */
+
+ /* Update transmit buffer control fields */
+ card->u.c.txbuf = ++txbuf;
+
+ if ((void*)txbuf > card->u.c.txbuf_last)
+ card->u.c.txbuf = card->u.c.txbuf_base;
+
+ return 0;
+}
+
+/****** Firmware Error Handler **********************************************/
+
+/*============================================================================
+ * Firmware error handler.
+ * This routine is called whenever firmware command returns non-zero
+ * return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb)
+{
+ unsigned cmd = mb->command;
+
+ switch (err) {
+
+ case CMD_TIMEOUT:
+ printk(KERN_INFO "%s: command 0x%02X timed out!\n",
+ card->devname, cmd);
+ break;
+
+ case S514_BOTH_PORTS_SAME_CLK_MODE:
+ if(cmd == SET_CHDLC_CONFIGURATION) {
+ printk(KERN_INFO
+ "%s: Configure both ports for the same clock source\n",
+ card->devname);
+ break;
+ }
+
+ default:
+ printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n",
+ card->devname, cmd, err);
+ }
+
+ return 0;
+}
+
+
+/********** Bottom Half Handlers ********************************************/
+
+/* NOTE: There is no API, BH support for Kernels lower than 2.2.X.
+ * DO NOT INSERT ANY CODE HERE, NOTICE THE
+ * PREPROCESSOR STATEMENT ABOVE, UNLESS YOU KNOW WHAT YOU ARE
+ * DOING */
+
+static void chdlc_work(struct net_device * dev)
+{
+ chdlc_private_area_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+ struct sk_buff *skb;
+
+ if (atomic_read(&chan->bh_buff_used) == 0){
+ clear_bit(0, &chan->tq_working);
+ return;
+ }
+
+ while (atomic_read(&chan->bh_buff_used)){
+
+ skb = ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb;
+
+ if (skb != NULL){
+
+ if (chan->common.sk == NULL || chan->common.func == NULL){
+ ++card->wandev.stats.rx_dropped;
+ dev_kfree_skb_any(skb);
+ chdlc_work_cleanup(dev);
+ continue;
+ }
+
+ if (chan->common.func(skb,dev,chan->common.sk) != 0){
+ /* Sock full cannot send, queue us for another
+ * try */
+ atomic_set(&chan->common.receive_block,1);
+ return;
+ }else{
+ chdlc_work_cleanup(dev);
+ }
+ }else{
+ chdlc_work_cleanup(dev);
+ }
+ }
+ clear_bit(0, &chan->tq_working);
+
+ return;
+}
+
+static int chdlc_work_cleanup(struct net_device *dev)
+{
+ chdlc_private_area_t* chan = dev->priv;
+
+ ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb = NULL;
+
+ if (chan->bh_read == MAX_BH_BUFF){
+ chan->bh_read=0;
+ }else{
+ ++chan->bh_read;
+ }
+
+ atomic_dec(&chan->bh_buff_used);
+ return 0;
+}
+
+
+
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb)
+{
+ /* Check for full */
+ chdlc_private_area_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ if (atomic_read(&chan->bh_buff_used) == (MAX_BH_BUFF+1)){
+ ++card->wandev.stats.rx_dropped;
+ dev_kfree_skb_any(skb);
+ return 1;
+ }
+
+ ((bh_data_t *)&chan->bh_head[chan->bh_write])->skb = skb;
+
+ if (chan->bh_write == MAX_BH_BUFF){
+ chan->bh_write=0;
+ }else{
+ ++chan->bh_write;
+ }
+
+ atomic_inc(&chan->bh_buff_used);
+
+ return 0;
+}
+
+/* END OF API BH Support */
+
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * Cisco HDLC interrupt service routine.
+ */
+static void wpc_isr (sdla_t* card)
+{
+ struct net_device* dev;
+ SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+ int i;
+ sdla_t *my_card;
+
+
+ /* Check for which port the interrupt has been generated
+ * Since Secondary Port is piggybacking on the Primary
+ * the check must be done here.
+ */
+
+ flags = card->u.c.flags;
+ if (!flags->interrupt_info_struct.interrupt_type){
+ /* Check for a second port (piggybacking) */
+ if ((my_card = card->next)){
+ flags = my_card->u.c.flags;
+ if (flags->interrupt_info_struct.interrupt_type){
+ card = my_card;
+ card->isr(card);
+ return;
+ }
+ }
+ }
+
+ flags = card->u.c.flags;
+ card->in_isr = 1;
+ dev = card->wandev.dev;
+
+ /* If we get an interrupt with no network device, stop the interrupts
+ * and issue an error */
+ if (!card->tty_opt && !dev &&
+ flags->interrupt_info_struct.interrupt_type !=
+ COMMAND_COMPLETE_APP_INT_PEND){
+
+ goto isr_done;
+ }
+
+ /* if critical due to peripheral operations
+ * ie. update() or getstats() then reset the interrupt and
+ * wait for the board to retrigger.
+ */
+ if(test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+ printk(KERN_INFO "ISR CRIT TO PERI\n");
+ goto isr_done;
+ }
+
+ /* On a 508 Card, if critical due to if_send
+ * Major Error !!! */
+ if(card->hw.type != SDLA_S514) {
+ if(test_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+ printk(KERN_INFO "%s: Critical while in ISR: %lx\n",
+ card->devname, card->wandev.critical);
+ card->in_isr = 0;
+ flags->interrupt_info_struct.interrupt_type = 0;
+ return;
+ }
+ }
+
+ switch(flags->interrupt_info_struct.interrupt_type) {
+
+ case RX_APP_INT_PEND: /* 0x01: receive interrupt */
+ rx_intr(card);
+ break;
+
+ case TX_APP_INT_PEND: /* 0x02: transmit interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TX_FRAME;
+
+ if (card->tty_opt){
+ wanpipe_tty_trigger_poll(card);
+ break;
+ }
+
+ if (dev && netif_queue_stopped(dev)){
+ if (card->u.c.usedby == API){
+ netif_start_queue(dev);
+ wakeup_sk_bh(dev);
+ }else{
+ netif_wake_queue(dev);
+ }
+ }
+ break;
+
+ case COMMAND_COMPLETE_APP_INT_PEND:/* 0x04: cmd cplt */
+ ++ Intr_test_counter;
+ break;
+
+ case CHDLC_EXCEP_COND_APP_INT_PEND: /* 0x20 */
+ process_chdlc_exception(card);
+ break;
+
+ case GLOBAL_EXCEP_COND_APP_INT_PEND:
+ process_global_exception(card);
+ break;
+
+ case TIMER_APP_INT_PEND:
+ timer_intr(card);
+ break;
+
+ default:
+ printk(KERN_INFO "%s: spurious interrupt 0x%02X!\n",
+ card->devname,
+ flags->interrupt_info_struct.interrupt_type);
+ printk(KERN_INFO "Code name: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codename[i]);
+ printk(KERN_INFO "\nCode version: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codeversion[i]);
+ printk(KERN_INFO "\n");
+ break;
+ }
+
+isr_done:
+
+ card->in_isr = 0;
+ flags->interrupt_info_struct.interrupt_type = 0;
+ return;
+}
+
+/*============================================================================
+ * Receive interrupt handler.
+ */
+static void rx_intr (sdla_t* card)
+{
+ struct net_device *dev;
+ chdlc_private_area_t *chdlc_priv_area;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ CHDLC_DATA_RX_STATUS_EL_STRUCT *rxbuf = card->u.c.rxmb;
+ struct sk_buff *skb;
+ unsigned len;
+ unsigned addr = rxbuf->ptr_data_bfr;
+ void *buf;
+ int i,udp_type;
+
+ if (rxbuf->opp_flag != 0x01) {
+ printk(KERN_INFO
+ "%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n",
+ card->devname, (unsigned)rxbuf, rxbuf->opp_flag);
+ printk(KERN_INFO "Code name: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codename[i]);
+ printk(KERN_INFO "\nCode version: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codeversion[i]);
+ printk(KERN_INFO "\n");
+
+
+ /* Bug Fix: Mar 6 2000
+ * If we get a corrupted mailbox, it measn that driver
+ * is out of sync with the firmware. There is no recovery.
+ * If we don't turn off all interrupts for this card
+ * the machine will crash.
+ */
+ printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+ printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+ chdlc_set_intr_mode(card,0);
+ return;
+ }
+
+ len = rxbuf->frame_length;
+
+ if (card->tty_opt){
+
+ if (rxbuf->error_flag){
+ goto rx_exit;
+ }
+
+ if (len <= CRC_LENGTH){
+ goto rx_exit;
+ }
+
+ if (!card->u.c.async_mode){
+ len -= CRC_LENGTH;
+ }
+
+ wanpipe_tty_receive(card,addr,len);
+ goto rx_exit;
+ }
+
+ dev = card->wandev.dev;
+
+ if (!dev){
+ goto rx_exit;
+ }
+
+ if (!netif_running(dev))
+ goto rx_exit;
+
+ chdlc_priv_area = dev->priv;
+
+
+ /* Allocate socket buffer */
+ skb = dev_alloc_skb(len);
+
+ if (skb == NULL) {
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ ++card->wandev.stats.rx_dropped;
+ goto rx_exit;
+ }
+
+ /* Copy data to the socket buffer */
+ if((addr + len) > card->u.c.rx_top + 1) {
+ unsigned tmp = card->u.c.rx_top - addr + 1;
+ buf = skb_put(skb, tmp);
+ sdla_peek(&card->hw, addr, buf, tmp);
+ addr = card->u.c.rx_base;
+ len -= tmp;
+ }
+
+ buf = skb_put(skb, len);
+ sdla_peek(&card->hw, addr, buf, len);
+
+ skb->protocol = htons(ETH_P_IP);
+
+ card->wandev.stats.rx_packets ++;
+ card->wandev.stats.rx_bytes += skb->len;
+ udp_type = udp_pkt_type( skb, card );
+
+ if(udp_type == UDP_CPIPE_TYPE) {
+ if(store_udp_mgmt_pkt(UDP_PKT_FRM_NETWORK,
+ card, skb, dev, chdlc_priv_area)) {
+ flags->interrupt_info_struct.
+ interrupt_permission |=
+ APP_INT_ON_TIMER;
+ }
+ } else if(card->u.c.usedby == API) {
+
+ api_rx_hdr_t* api_rx_hdr;
+ skb_push(skb, sizeof(api_rx_hdr_t));
+ api_rx_hdr = (api_rx_hdr_t*)&skb->data[0x00];
+ api_rx_hdr->error_flag = rxbuf->error_flag;
+ api_rx_hdr->time_stamp = rxbuf->time_stamp;
+
+ skb->protocol = htons(PVC_PROT);
+ skb->mac.raw = skb->data;
+ skb->dev = dev;
+ skb->pkt_type = WAN_PACKET_DATA;
+
+ bh_enqueue(dev, skb);
+
+ if (!test_and_set_bit(0,&chdlc_priv_area->tq_working))
+ wanpipe_queue_work(&chdlc_priv_area->common.wanpipe_work);
+ }else{
+ /* FIXME: we should check to see if the received packet is a
+ multicast packet so that we can increment the multicast
+ statistic
+ ++ chdlc_priv_area->if_stats.multicast;
+ */
+ /* Pass it up the protocol stack */
+
+ skb->dev = dev;
+ skb->mac.raw = skb->data;
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ }
+
+rx_exit:
+ /* Release buffer element and calculate a pointer to the next one */
+ rxbuf->opp_flag = 0x00;
+ card->u.c.rxmb = ++ rxbuf;
+ if((void*)rxbuf > card->u.c.rxbuf_last){
+ card->u.c.rxmb = card->u.c.rxbuf_base;
+ }
+}
+
+/*============================================================================
+ * Timer interrupt handler.
+ * The timer interrupt is used for two purposes:
+ * 1) Processing udp calls from 'cpipemon'.
+ * 2) Reading board-level statistics for updating the proc file system.
+ */
+void timer_intr(sdla_t *card)
+{
+ struct net_device* dev;
+ chdlc_private_area_t* chdlc_priv_area = NULL;
+ SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+
+ if ((dev = card->wandev.dev)==NULL){
+ flags = card->u.c.flags;
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TIMER;
+ return;
+ }
+
+ chdlc_priv_area = dev->priv;
+
+ if (chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_CONFIG) {
+ if (!config_chdlc(card)){
+ chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_CONFIG;
+ }
+ }
+
+ /* process a udp call if pending */
+ if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UDP) {
+ process_udp_mgmt_pkt(card, dev,
+ chdlc_priv_area);
+ chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_UDP;
+ }
+
+ /* read the communications statistics if required */
+ if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UPDATE) {
+ update_comms_stats(card, chdlc_priv_area);
+ if(!(-- chdlc_priv_area->update_comms_stats)) {
+ chdlc_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_UPDATE;
+ }
+ }
+
+ /* only disable the timer interrupt if there are no udp or statistic */
+ /* updates pending */
+ if(!chdlc_priv_area->timer_int_enabled) {
+ flags = card->u.c.flags;
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TIMER;
+ }
+}
+
+/*------------------------------------------------------------------------------
+ Miscellaneous Functions
+ - set_chdlc_config() used to set configuration options on the board
+------------------------------------------------------------------------------*/
+
+static int set_chdlc_config(sdla_t* card)
+{
+ CHDLC_CONFIGURATION_STRUCT cfg;
+
+ memset(&cfg, 0, sizeof(CHDLC_CONFIGURATION_STRUCT));
+
+ if(card->wandev.clocking){
+ cfg.baud_rate = card->wandev.bps;
+ }
+
+ cfg.line_config_options = (card->wandev.interface == WANOPT_RS232) ?
+ INTERFACE_LEVEL_RS232 : INTERFACE_LEVEL_V35;
+
+ cfg.modem_config_options = 0;
+ cfg.modem_status_timer = 100;
+
+ cfg.CHDLC_protocol_options = card->u.c.protocol_options;
+
+ if (card->tty_opt){
+ cfg.CHDLC_API_options = DISCARD_RX_ERROR_FRAMES;
+ }
+
+ cfg.percent_data_buffer_for_Tx = (card->u.c.receive_only) ? 0 : 50;
+ cfg.CHDLC_statistics_options = (CHDLC_TX_DATA_BYTE_COUNT_STAT |
+ CHDLC_RX_DATA_BYTE_COUNT_STAT);
+
+ if (card->tty_opt){
+ card->wandev.mtu = TTY_CHDLC_MAX_MTU;
+ }
+ cfg.max_CHDLC_data_field_length = card->wandev.mtu;
+ cfg.transmit_keepalive_timer = card->u.c.kpalv_tx;
+ cfg.receive_keepalive_timer = card->u.c.kpalv_rx;
+ cfg.keepalive_error_tolerance = card->u.c.kpalv_err;
+ cfg.SLARP_request_timer = card->u.c.slarp_timer;
+
+ if (cfg.SLARP_request_timer) {
+ cfg.IP_address = 0;
+ cfg.IP_netmask = 0;
+
+ }else if (card->wandev.dev){
+ struct net_device *dev = card->wandev.dev;
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+
+ struct in_device *in_dev = dev->ip_ptr;
+
+ if(in_dev != NULL) {
+ struct in_ifaddr *ifa = in_dev->ifa_list;
+
+ if (ifa != NULL ) {
+ cfg.IP_address = ntohl(ifa->ifa_local);
+ cfg.IP_netmask = ntohl(ifa->ifa_mask);
+ chdlc_priv_area->IP_address = ntohl(ifa->ifa_local);
+ chdlc_priv_area->IP_netmask = ntohl(ifa->ifa_mask);
+ }
+ }
+
+ /* FIXME: We must re-think this message in next release
+ if((cfg.IP_address & 0x000000FF) > 2) {
+ printk(KERN_WARNING "\n");
+ printk(KERN_WARNING " WARNING:%s configured with an\n",
+ card->devname);
+ printk(KERN_WARNING " invalid local IP address.\n");
+ printk(KERN_WARNING " Slarp pragmatics will fail.\n");
+ printk(KERN_WARNING " IP address should be of the\n");
+ printk(KERN_WARNING " format A.B.C.1 or A.B.C.2.\n");
+ }
+ */
+ }
+
+ return chdlc_configure(card, &cfg);
+}
+
+
+/*-----------------------------------------------------------------------------
+ set_asy_config() used to set asynchronous configuration options on the board
+------------------------------------------------------------------------------*/
+
+static int set_asy_config(sdla_t* card)
+{
+
+ ASY_CONFIGURATION_STRUCT cfg;
+ CHDLC_MAILBOX_STRUCT *mailbox = card->mbox;
+ int err;
+
+ memset(&cfg, 0, sizeof(ASY_CONFIGURATION_STRUCT));
+
+ if(card->wandev.clocking)
+ cfg.baud_rate = card->wandev.bps;
+
+ cfg.line_config_options = (card->wandev.interface == WANOPT_RS232) ?
+ INTERFACE_LEVEL_RS232 : INTERFACE_LEVEL_V35;
+
+ cfg.modem_config_options = 0;
+ cfg.asy_API_options = card->u.c.api_options;
+ cfg.asy_protocol_options = card->u.c.protocol_options;
+ cfg.Tx_bits_per_char = card->u.c.tx_bits_per_char;
+ cfg.Rx_bits_per_char = card->u.c.rx_bits_per_char;
+ cfg.stop_bits = card->u.c.stop_bits;
+ cfg.parity = card->u.c.parity;
+ cfg.break_timer = card->u.c.break_timer;
+ cfg.asy_Rx_inter_char_timer = card->u.c.inter_char_timer;
+ cfg.asy_Rx_complete_length = card->u.c.rx_complete_length;
+ cfg.XON_char = card->u.c.xon_char;
+ cfg.XOFF_char = card->u.c.xoff_char;
+ cfg.asy_statistics_options = (CHDLC_TX_DATA_BYTE_COUNT_STAT |
+ CHDLC_RX_DATA_BYTE_COUNT_STAT);
+
+ mailbox->buffer_length = sizeof(ASY_CONFIGURATION_STRUCT);
+ memcpy(mailbox->data, &cfg, mailbox->buffer_length);
+ mailbox->command = SET_ASY_CONFIGURATION;
+ err = sdla_exec(mailbox) ? mailbox->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error (card, err, mailbox);
+ return err;
+}
+
+/*============================================================================
+ * Enable asynchronous communications.
+ */
+
+static int asy_comm_enable (sdla_t* card)
+{
+
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = ENABLE_ASY_COMMUNICATIONS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK && card->wandev.dev)
+ chdlc_error(card, err, mb);
+
+ if (!err)
+ card->u.c.comm_enabled = 1;
+
+ return err;
+}
+
+/*============================================================================
+ * Process global exception condition
+ */
+static int process_global_exception(sdla_t *card)
+{
+ CHDLC_MAILBOX_STRUCT* mbox = card->mbox;
+ int err;
+
+ mbox->buffer_length = 0;
+ mbox->command = READ_GLOBAL_EXCEPTION_CONDITION;
+ err = sdla_exec(mbox) ? mbox->return_code : CMD_TIMEOUT;
+
+ if(err != CMD_TIMEOUT ){
+
+ switch(mbox->return_code) {
+
+ case EXCEP_MODEM_STATUS_CHANGE:
+
+ printk(KERN_INFO "%s: Modem status change\n",
+ card->devname);
+
+ switch(mbox->data[0] & (DCD_HIGH | CTS_HIGH)) {
+ case (DCD_HIGH):
+ printk(KERN_INFO "%s: DCD high, CTS low\n",card->devname);
+ break;
+ case (CTS_HIGH):
+ printk(KERN_INFO "%s: DCD low, CTS high\n",card->devname);
+ break;
+ case ((DCD_HIGH | CTS_HIGH)):
+ printk(KERN_INFO "%s: DCD high, CTS high\n",card->devname);
+ break;
+ default:
+ printk(KERN_INFO "%s: DCD low, CTS low\n",card->devname);
+ break;
+ }
+ break;
+
+ case EXCEP_TRC_DISABLED:
+ printk(KERN_INFO "%s: Line trace disabled\n",
+ card->devname);
+ break;
+
+ case EXCEP_IRQ_TIMEOUT:
+ printk(KERN_INFO "%s: IRQ timeout occurred\n",
+ card->devname);
+ break;
+
+ case 0x17:
+ if (card->tty_opt){
+ if (card->tty && card->tty_open){
+ printk(KERN_INFO
+ "%s: Modem Hangup Exception: Hanging Up!\n",
+ card->devname);
+ tty_hangup(card->tty);
+ }
+ break;
+ }
+
+ /* If TTY is not used just drop throught */
+
+ default:
+ printk(KERN_INFO "%s: Global exception %x\n",
+ card->devname, mbox->return_code);
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/*============================================================================
+ * Process chdlc exception condition
+ */
+static int process_chdlc_exception(sdla_t *card)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int err;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_EXCEPTION_CONDITION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if(err != CMD_TIMEOUT) {
+
+ switch (err) {
+
+ case EXCEP_LINK_ACTIVE:
+ port_set_state(card, WAN_CONNECTED);
+ trigger_chdlc_poll(card->wandev.dev);
+ break;
+
+ case EXCEP_LINK_INACTIVE_MODEM:
+ port_set_state(card, WAN_DISCONNECTED);
+ unconfigure_ip(card);
+ trigger_chdlc_poll(card->wandev.dev);
+ break;
+
+ case EXCEP_LINK_INACTIVE_KPALV:
+ port_set_state(card, WAN_DISCONNECTED);
+ printk(KERN_INFO "%s: Keepalive timer expired.\n",
+ card->devname);
+ unconfigure_ip(card);
+ trigger_chdlc_poll(card->wandev.dev);
+ break;
+
+ case EXCEP_IP_ADDRESS_DISCOVERED:
+ if (configure_ip(card))
+ return -1;
+ break;
+
+ case EXCEP_LOOPBACK_CONDITION:
+ printk(KERN_INFO "%s: Loopback Condition Detected.\n",
+ card->devname);
+ break;
+
+ case NO_CHDLC_EXCEP_COND_TO_REPORT:
+ printk(KERN_INFO "%s: No exceptions reported.\n",
+ card->devname);
+ break;
+ }
+
+ }
+ return 0;
+}
+
+
+/*============================================================================
+ * Configure IP from SLARP negotiation
+ * This adds dynamic routes when SLARP has provided valid addresses
+ */
+
+static int configure_ip (sdla_t* card)
+{
+ struct net_device *dev = card->wandev.dev;
+ chdlc_private_area_t *chdlc_priv_area;
+ char err;
+
+ if (!dev)
+ return 0;
+
+ chdlc_priv_area = dev->priv;
+
+
+ /* set to discover */
+ if(card->u.c.slarp_timer != 0x00) {
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ CHDLC_CONFIGURATION_STRUCT *cfg;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CONFIGURATION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if(err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ return -1;
+ }
+
+ cfg = (CHDLC_CONFIGURATION_STRUCT *)mb->data;
+ chdlc_priv_area->IP_address = cfg->IP_address;
+ chdlc_priv_area->IP_netmask = cfg->IP_netmask;
+
+ /* Set flag to add route */
+ chdlc_priv_area->route_status = ADD_ROUTE;
+
+ /* The idea here is to add the route in the poll routine.
+ This way, we aren't in interrupt context when adding routes */
+ trigger_chdlc_poll(dev);
+ }
+
+ return 0;
+}
+
+
+/*============================================================================
+ * Un-Configure IP negotiated by SLARP
+ * This removes dynamic routes when the link becomes inactive.
+ */
+
+static int unconfigure_ip (sdla_t* card)
+{
+ struct net_device *dev = card->wandev.dev;
+ chdlc_private_area_t *chdlc_priv_area;
+
+ if (!dev)
+ return 0;
+
+ chdlc_priv_area= dev->priv;
+
+ if (chdlc_priv_area->route_status == ROUTE_ADDED) {
+
+ /* Note: If this function is called, the
+ * port state has been DISCONNECTED. This state
+ * change will trigger a poll_disconnected
+ * function, that will check for this condition.
+ */
+ chdlc_priv_area->route_status = REMOVE_ROUTE;
+
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Routine to add/remove routes
+ * Called like a polling routine when Routes are flagged to be added/removed.
+ */
+
+static void process_route (sdla_t *card)
+{
+ struct net_device *dev = card->wandev.dev;
+ unsigned char port_num;
+ chdlc_private_area_t *chdlc_priv_area = NULL;
+ u32 local_IP_addr = 0;
+ u32 remote_IP_addr = 0;
+ u32 IP_netmask, IP_addr;
+ int err = 0;
+ struct in_device *in_dev;
+ mm_segment_t fs;
+ struct ifreq if_info;
+ struct sockaddr_in *if_data1, *if_data2;
+
+ chdlc_priv_area = dev->priv;
+ port_num = card->u.c.comm_port;
+
+ /* Bug Fix Mar 16 2000
+ * AND the IP address to the Mask before checking
+ * the last two bits. */
+
+ if((chdlc_priv_area->route_status == ADD_ROUTE) &&
+ ((chdlc_priv_area->IP_address & ~chdlc_priv_area->IP_netmask) > 2)) {
+
+ printk(KERN_INFO "%s: Dynamic route failure.\n",card->devname);
+
+ if(card->u.c.slarp_timer) {
+ u32 addr_net = htonl(chdlc_priv_area->IP_address);
+
+ printk(KERN_INFO "%s: Bad IP address %u.%u.%u.%u received\n",
+ card->devname,
+ NIPQUAD(addr_net));
+ printk(KERN_INFO "%s: from remote station.\n",
+ card->devname);
+
+ }else{
+ u32 addr_net = htonl(chdlc_priv_area->IP_address);
+
+ printk(KERN_INFO "%s: Bad IP address %u.%u.%u.%u issued\n",
+ card->devname,
+ NIPQUAD(addr_net));
+ printk(KERN_INFO "%s: to remote station. Local\n",
+ card->devname);
+ printk(KERN_INFO "%s: IP address must be A.B.C.1\n",
+ card->devname);
+ printk(KERN_INFO "%s: or A.B.C.2.\n",card->devname);
+ }
+
+ /* remove the route due to the IP address error condition */
+ chdlc_priv_area->route_status = REMOVE_ROUTE;
+ err = 1;
+ }
+
+ /* If we are removing a route with bad IP addressing, then use the */
+ /* locally configured IP addresses */
+ if((chdlc_priv_area->route_status == REMOVE_ROUTE) && err) {
+
+ /* do not remove a bad route that has already been removed */
+ if(chdlc_priv_area->route_removed) {
+ return;
+ }
+
+ in_dev = dev->ip_ptr;
+
+ if(in_dev != NULL) {
+ struct in_ifaddr *ifa = in_dev->ifa_list;
+ if (ifa != NULL ) {
+ local_IP_addr = ifa->ifa_local;
+ IP_netmask = ifa->ifa_mask;
+ }
+ }
+ }else{
+ /* According to Cisco HDLC, if the point-to-point address is
+ A.B.C.1, then we are the opposite (A.B.C.2), and vice-versa.
+ */
+ IP_netmask = ntohl(chdlc_priv_area->IP_netmask);
+ remote_IP_addr = ntohl(chdlc_priv_area->IP_address);
+
+
+ /* If Netmask is 255.255.255.255 the local address
+ * calculation will fail. Default it back to 255.255.255.0 */
+ if (IP_netmask == 0xffffffff)
+ IP_netmask &= 0x00ffffff;
+
+ /* Bug Fix Mar 16 2000
+ * AND the Remote IP address with IP netmask, instead
+ * of static netmask of 255.255.255.0 */
+ local_IP_addr = (remote_IP_addr & IP_netmask) +
+ (~remote_IP_addr & ntohl(0x0003));
+
+ if(!card->u.c.slarp_timer) {
+ IP_addr = local_IP_addr;
+ local_IP_addr = remote_IP_addr;
+ remote_IP_addr = IP_addr;
+ }
+ }
+
+ fs = get_fs(); /* Save file system */
+ set_fs(get_ds()); /* Get user space block */
+
+ /* Setup a structure for adding/removing routes */
+ memset(&if_info, 0, sizeof(if_info));
+ strcpy(if_info.ifr_name, dev->name);
+
+ switch (chdlc_priv_area->route_status) {
+
+ case ADD_ROUTE:
+
+ if(!card->u.c.slarp_timer) {
+ if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+ if_data2->sin_addr.s_addr = remote_IP_addr;
+ if_data2->sin_family = AF_INET;
+ err = devinet_ioctl(SIOCSIFDSTADDR, &if_info);
+ } else {
+ if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+ if_data1->sin_addr.s_addr = local_IP_addr;
+ if_data1->sin_family = AF_INET;
+ if(!(err = devinet_ioctl(SIOCSIFADDR, &if_info))){
+ if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+ if_data2->sin_addr.s_addr = remote_IP_addr;
+ if_data2->sin_family = AF_INET;
+ err = devinet_ioctl(SIOCSIFDSTADDR, &if_info);
+ }
+ }
+
+ if(err) {
+ printk(KERN_INFO "%s: Add route %u.%u.%u.%u failed (%d)\n",
+ card->devname, NIPQUAD(remote_IP_addr), err);
+ } else {
+ ((chdlc_private_area_t *)dev->priv)->route_status = ROUTE_ADDED;
+ printk(KERN_INFO "%s: Dynamic route added.\n",
+ card->devname);
+ printk(KERN_INFO "%s: Local IP addr : %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(local_IP_addr));
+ printk(KERN_INFO "%s: Remote IP addr: %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(remote_IP_addr));
+ chdlc_priv_area->route_removed = 0;
+ }
+ break;
+
+
+ case REMOVE_ROUTE:
+
+ /* Change the local ip address of the interface to 0.
+ * This will also delete the destination route.
+ */
+ if(!card->u.c.slarp_timer) {
+ if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+ if_data2->sin_addr.s_addr = 0;
+ if_data2->sin_family = AF_INET;
+ err = devinet_ioctl(SIOCSIFDSTADDR, &if_info);
+ } else {
+ if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+ if_data1->sin_addr.s_addr = 0;
+ if_data1->sin_family = AF_INET;
+ err = devinet_ioctl(SIOCSIFADDR,&if_info);
+
+ }
+ if(err) {
+ printk(KERN_INFO
+ "%s: Remove route %u.%u.%u.%u failed, (err %d)\n",
+ card->devname, NIPQUAD(remote_IP_addr),
+ err);
+ } else {
+ ((chdlc_private_area_t *)dev->priv)->route_status =
+ NO_ROUTE;
+ printk(KERN_INFO "%s: Dynamic route removed: %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(local_IP_addr));
+ chdlc_priv_area->route_removed = 1;
+ }
+ break;
+ }
+
+ set_fs(fs); /* Restore file system */
+
+}
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area)
+{
+ int udp_pkt_stored = 0;
+
+ if(!chdlc_priv_area->udp_pkt_lgth &&
+ (skb->len <= MAX_LGTH_UDP_MGNT_PKT)) {
+ chdlc_priv_area->udp_pkt_lgth = skb->len;
+ chdlc_priv_area->udp_pkt_src = udp_pkt_src;
+ memcpy(chdlc_priv_area->udp_pkt_data, skb->data, skb->len);
+ chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UDP;
+ udp_pkt_stored = 1;
+ }
+
+ if(udp_pkt_src == UDP_PKT_FRM_STACK){
+ dev_kfree_skb_any(skb);
+ }else{
+ dev_kfree_skb_any(skb);
+ }
+
+ return(udp_pkt_stored);
+}
+
+
+/*=============================================================================
+ * Process UDP management packet.
+ */
+
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area )
+{
+ unsigned char *buf;
+ unsigned int frames, len;
+ struct sk_buff *new_skb;
+ unsigned short buffer_length, real_len;
+ unsigned long data_ptr;
+ unsigned data_length;
+ int udp_mgmt_req_valid = 1;
+ CHDLC_MAILBOX_STRUCT *mb = card->mbox;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ chdlc_udp_pkt_t *chdlc_udp_pkt;
+ struct timeval tv;
+ int err;
+ char ut_char;
+
+ chdlc_udp_pkt = (chdlc_udp_pkt_t *) chdlc_priv_area->udp_pkt_data;
+
+ if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK){
+
+ /* Only these commands are support for remote debugging.
+ * All others are not */
+ switch(chdlc_udp_pkt->cblock.command) {
+
+ case READ_GLOBAL_STATISTICS:
+ case READ_MODEM_STATUS:
+ case READ_CHDLC_LINK_STATUS:
+ case CPIPE_ROUTER_UP_TIME:
+ case READ_COMMS_ERROR_STATS:
+ case READ_CHDLC_OPERATIONAL_STATS:
+
+ /* These two commands are executed for
+ * each request */
+ case READ_CHDLC_CONFIGURATION:
+ case READ_CHDLC_CODE_VERSION:
+ udp_mgmt_req_valid = 1;
+ break;
+ default:
+ udp_mgmt_req_valid = 0;
+ break;
+ }
+ }
+
+ if(!udp_mgmt_req_valid) {
+
+ /* set length to 0 */
+ chdlc_udp_pkt->cblock.buffer_length = 0;
+
+ /* set return code */
+ chdlc_udp_pkt->cblock.return_code = 0xCD;
+
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Warning, Illegal UDP command attempted from network: %x\n",
+ card->devname,chdlc_udp_pkt->cblock.command);
+ }
+
+ } else {
+ unsigned long trace_status_cfg_addr = 0;
+ TRACE_STATUS_EL_CFG_STRUCT trace_cfg_struct;
+ TRACE_STATUS_ELEMENT_STRUCT trace_element_struct;
+
+ switch(chdlc_udp_pkt->cblock.command) {
+
+ case CPIPE_ENABLE_TRACING:
+ if (!chdlc_priv_area->TracingEnabled) {
+
+ /* OPERATE_DATALINE_MONITOR */
+
+ mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+ mb->command = SET_TRACE_CONFIGURATION;
+
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+ trace_config = TRACE_ACTIVE;
+ /* Trace delay mode is not used because it slows
+ down transfer and results in a standoff situation
+ when there is a lot of data */
+
+ /* Configure the Trace based on user inputs */
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->trace_config |=
+ chdlc_udp_pkt->data[0];
+
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+ trace_deactivation_timer = 4000;
+
+
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ card->TracingEnabled = 0;
+ chdlc_udp_pkt->cblock.return_code = err;
+ mb->buffer_length = 0;
+ break;
+ }
+
+ /* Get the base address of the trace element list */
+ mb->buffer_length = 0;
+ mb->command = READ_TRACE_CONFIGURATION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if (err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ chdlc_priv_area->TracingEnabled = 0;
+ chdlc_udp_pkt->cblock.return_code = err;
+ mb->buffer_length = 0;
+ break;
+ }
+
+ trace_status_cfg_addr =((LINE_TRACE_CONFIG_STRUCT *)
+ mb->data) -> ptr_trace_stat_el_cfg_struct;
+
+ sdla_peek(&card->hw, trace_status_cfg_addr,
+ &trace_cfg_struct, sizeof(trace_cfg_struct));
+
+ chdlc_priv_area->start_trace_addr = trace_cfg_struct.
+ base_addr_trace_status_elements;
+
+ chdlc_priv_area->number_trace_elements =
+ trace_cfg_struct.number_trace_status_elements;
+
+ chdlc_priv_area->end_trace_addr = (unsigned long)
+ ((TRACE_STATUS_ELEMENT_STRUCT *)
+ chdlc_priv_area->start_trace_addr +
+ (chdlc_priv_area->number_trace_elements - 1));
+
+ chdlc_priv_area->base_addr_trace_buffer =
+ trace_cfg_struct.base_addr_trace_buffer;
+
+ chdlc_priv_area->end_addr_trace_buffer =
+ trace_cfg_struct.end_addr_trace_buffer;
+
+ chdlc_priv_area->curr_trace_addr =
+ trace_cfg_struct.next_trace_element_to_use;
+
+ chdlc_priv_area->available_buffer_space = 2000 -
+ sizeof(ip_pkt_t) -
+ sizeof(udp_pkt_t) -
+ sizeof(wp_mgmt_t) -
+ sizeof(cblock_t) -
+ sizeof(trace_info_t);
+ }
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ mb->buffer_length = 0;
+ chdlc_priv_area->TracingEnabled = 1;
+ break;
+
+
+ case CPIPE_DISABLE_TRACING:
+ if (chdlc_priv_area->TracingEnabled) {
+
+ /* OPERATE_DATALINE_MONITOR */
+ mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+ mb->command = SET_TRACE_CONFIGURATION;
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+ trace_config = TRACE_INACTIVE;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ }
+
+ chdlc_priv_area->TracingEnabled = 0;
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ mb->buffer_length = 0;
+ break;
+
+
+ case CPIPE_GET_TRACE_INFO:
+
+ if (!chdlc_priv_area->TracingEnabled) {
+ chdlc_udp_pkt->cblock.return_code = 1;
+ mb->buffer_length = 0;
+ break;
+ }
+
+ chdlc_udp_pkt->trace_info.ismoredata = 0x00;
+ buffer_length = 0; /* offset of packet already occupied */
+
+ for (frames=0; frames < chdlc_priv_area->number_trace_elements; frames++){
+
+ trace_pkt_t *trace_pkt = (trace_pkt_t *)
+ &chdlc_udp_pkt->data[buffer_length];
+
+ sdla_peek(&card->hw, chdlc_priv_area->curr_trace_addr,
+ (unsigned char *)&trace_element_struct,
+ sizeof(TRACE_STATUS_ELEMENT_STRUCT));
+
+ if (trace_element_struct.opp_flag == 0x00) {
+ break;
+ }
+
+ /* get pointer to real data */
+ data_ptr = trace_element_struct.ptr_data_bfr;
+
+ /* See if there is actual data on the trace buffer */
+ if (data_ptr){
+ data_length = trace_element_struct.trace_length;
+ }else{
+ data_length = 0;
+ chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+ }
+
+ if( (chdlc_priv_area->available_buffer_space - buffer_length)
+ < ( sizeof(trace_pkt_t) + data_length) ) {
+
+ /* indicate there are more frames on board & exit */
+ chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+ break;
+ }
+
+ trace_pkt->status = trace_element_struct.trace_type;
+
+ trace_pkt->time_stamp =
+ trace_element_struct.trace_time_stamp;
+
+ trace_pkt->real_length =
+ trace_element_struct.trace_length;
+
+ /* see if we can fit the frame into the user buffer */
+ real_len = trace_pkt->real_length;
+
+ if (data_ptr == 0) {
+ trace_pkt->data_avail = 0x00;
+ } else {
+ unsigned tmp = 0;
+
+ /* get the data from circular buffer
+ must check for end of buffer */
+ trace_pkt->data_avail = 0x01;
+
+ if ((data_ptr + real_len) >
+ chdlc_priv_area->end_addr_trace_buffer + 1){
+
+ tmp = chdlc_priv_area->end_addr_trace_buffer - data_ptr + 1;
+ sdla_peek(&card->hw, data_ptr,
+ trace_pkt->data,tmp);
+ data_ptr = chdlc_priv_area->base_addr_trace_buffer;
+ }
+
+ sdla_peek(&card->hw, data_ptr,
+ &trace_pkt->data[tmp], real_len - tmp);
+ }
+
+ /* zero the opp flag to show we got the frame */
+ ut_char = 0x00;
+ sdla_poke(&card->hw, chdlc_priv_area->curr_trace_addr, &ut_char, 1);
+
+ /* now move onto the next frame */
+ chdlc_priv_area->curr_trace_addr += sizeof(TRACE_STATUS_ELEMENT_STRUCT);
+
+ /* check if we went over the last address */
+ if ( chdlc_priv_area->curr_trace_addr > chdlc_priv_area->end_trace_addr ) {
+ chdlc_priv_area->curr_trace_addr = chdlc_priv_area->start_trace_addr;
+ }
+
+ if(trace_pkt->data_avail == 0x01) {
+ buffer_length += real_len - 1;
+ }
+
+ /* for the header */
+ buffer_length += sizeof(trace_pkt_t);
+
+ } /* For Loop */
+
+ if (frames == chdlc_priv_area->number_trace_elements){
+ chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+ }
+ chdlc_udp_pkt->trace_info.num_frames = frames;
+
+ mb->buffer_length = buffer_length;
+ chdlc_udp_pkt->cblock.buffer_length = buffer_length;
+
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+
+ break;
+
+
+ case CPIPE_FT1_READ_STATUS:
+ ((unsigned char *)chdlc_udp_pkt->data )[0] =
+ flags->FT1_info_struct.parallel_port_A_input;
+
+ ((unsigned char *)chdlc_udp_pkt->data )[1] =
+ flags->FT1_info_struct.parallel_port_B_input;
+
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ chdlc_udp_pkt->cblock.buffer_length = 2;
+ mb->buffer_length = 2;
+ break;
+
+ case CPIPE_ROUTER_UP_TIME:
+ do_gettimeofday( &tv );
+ chdlc_priv_area->router_up_time = tv.tv_sec -
+ chdlc_priv_area->router_start_time;
+ *(unsigned long *)&chdlc_udp_pkt->data =
+ chdlc_priv_area->router_up_time;
+ mb->buffer_length = sizeof(unsigned long);
+ chdlc_udp_pkt->cblock.buffer_length = sizeof(unsigned long);
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ break;
+
+ case FT1_MONITOR_STATUS_CTRL:
+ /* Enable FT1 MONITOR STATUS */
+ if ((chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_STATUS) ||
+ (chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_OP_STATS)) {
+
+ if( rCount++ != 0 ) {
+ chdlc_udp_pkt->cblock.
+ return_code = COMMAND_OK;
+ mb->buffer_length = 1;
+ break;
+ }
+ }
+
+ /* Disable FT1 MONITOR STATUS */
+ if( chdlc_udp_pkt->data[0] == 0) {
+
+ if( --rCount != 0) {
+ chdlc_udp_pkt->cblock.
+ return_code = COMMAND_OK;
+ mb->buffer_length = 1;
+ break;
+ }
+ }
+ goto dflt_1;
+
+ default:
+dflt_1:
+ /* it's a board command */
+ mb->command = chdlc_udp_pkt->cblock.command;
+ mb->buffer_length = chdlc_udp_pkt->cblock.buffer_length;
+ if (mb->buffer_length) {
+ memcpy(&mb->data, (unsigned char *) chdlc_udp_pkt->
+ data, mb->buffer_length);
+ }
+ /* run the command on the board */
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK) {
+ break;
+ }
+
+ /* copy the result back to our buffer */
+ memcpy(&chdlc_udp_pkt->cblock, mb, sizeof(cblock_t));
+
+ if (mb->buffer_length) {
+ memcpy(&chdlc_udp_pkt->data, &mb->data,
+ mb->buffer_length);
+ }
+
+ } /* end of switch */
+ } /* end of else */
+
+ /* Fill UDP TTL */
+ chdlc_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+
+ len = reply_udp(chdlc_priv_area->udp_pkt_data, mb->buffer_length);
+
+
+ if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK){
+
+ /* Must check if we interrupted if_send() routine. The
+ * tx buffers might be used. If so drop the packet */
+ if (!test_bit(SEND_CRIT,&card->wandev.critical)) {
+
+ if(!chdlc_send(card, chdlc_priv_area->udp_pkt_data, len)) {
+ ++ card->wandev.stats.tx_packets;
+ card->wandev.stats.tx_bytes += len;
+ }
+ }
+ } else {
+
+ /* Pass it up the stack
+ Allocate socket buffer */
+ if ((new_skb = dev_alloc_skb(len)) != NULL) {
+ /* copy data into new_skb */
+
+ buf = skb_put(new_skb, len);
+ memcpy(buf, chdlc_priv_area->udp_pkt_data, len);
+
+ /* Decapsulate pkt and pass it up the protocol stack */
+ new_skb->protocol = htons(ETH_P_IP);
+ new_skb->dev = dev;
+ new_skb->mac.raw = new_skb->data;
+
+ netif_rx(new_skb);
+ dev->last_rx = jiffies;
+ } else {
+
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ }
+ }
+
+ chdlc_priv_area->udp_pkt_lgth = 0;
+
+ return 0;
+}
+
+/*============================================================================
+ * Initialize Receive and Transmit Buffers.
+ */
+
+static void init_chdlc_tx_rx_buff( sdla_t* card)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ CHDLC_TX_STATUS_EL_CFG_STRUCT *tx_config;
+ CHDLC_RX_STATUS_EL_CFG_STRUCT *rx_config;
+ char err;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CONFIGURATION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if(err != COMMAND_OK) {
+ if (card->wandev.dev){
+ chdlc_error(card,err,mb);
+ }
+ return;
+ }
+
+ if(card->hw.type == SDLA_S514) {
+ tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Tx_stat_el_cfg_struct));
+ rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Rx_stat_el_cfg_struct));
+
+ /* Setup Head and Tails for buffers */
+ card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+ tx_config->base_addr_Tx_status_elements);
+ card->u.c.txbuf_last =
+ (CHDLC_DATA_TX_STATUS_EL_STRUCT *)
+ card->u.c.txbuf_base +
+ (tx_config->number_Tx_status_elements - 1);
+
+ card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+ rx_config->base_addr_Rx_status_elements);
+ card->u.c.rxbuf_last =
+ (CHDLC_DATA_RX_STATUS_EL_STRUCT *)
+ card->u.c.rxbuf_base +
+ (rx_config->number_Rx_status_elements - 1);
+
+ /* Set up next pointer to be used */
+ card->u.c.txbuf = (void *)(card->hw.dpmbase +
+ tx_config->next_Tx_status_element_to_use);
+ card->u.c.rxmb = (void *)(card->hw.dpmbase +
+ rx_config->next_Rx_status_element_to_use);
+ }
+ else {
+ tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Tx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+ rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Rx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+ /* Setup Head and Tails for buffers */
+ card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+ (tx_config->base_addr_Tx_status_elements % SDLA_WINDOWSIZE));
+ card->u.c.txbuf_last =
+ (CHDLC_DATA_TX_STATUS_EL_STRUCT *)card->u.c.txbuf_base
+ + (tx_config->number_Tx_status_elements - 1);
+ card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+ (rx_config->base_addr_Rx_status_elements % SDLA_WINDOWSIZE));
+ card->u.c.rxbuf_last =
+ (CHDLC_DATA_RX_STATUS_EL_STRUCT *)card->u.c.rxbuf_base
+ + (rx_config->number_Rx_status_elements - 1);
+
+ /* Set up next pointer to be used */
+ card->u.c.txbuf = (void *)(card->hw.dpmbase +
+ (tx_config->next_Tx_status_element_to_use % SDLA_WINDOWSIZE));
+ card->u.c.rxmb = (void *)(card->hw.dpmbase +
+ (rx_config->next_Rx_status_element_to_use % SDLA_WINDOWSIZE));
+ }
+
+ /* Setup Actual Buffer Start and end addresses */
+ card->u.c.rx_base = rx_config->base_addr_Rx_buffer;
+ card->u.c.rx_top = rx_config->end_addr_Rx_buffer;
+
+}
+
+/*=============================================================================
+ * Perform Interrupt Test by running READ_CHDLC_CODE_VERSION command MAX_INTR
+ * _TEST_COUNTER times.
+ */
+static int intr_test( sdla_t* card)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int err,i;
+
+ Intr_test_counter = 0;
+
+ err = chdlc_set_intr_mode(card, APP_INT_ON_COMMAND_COMPLETE);
+
+ if (err == CMD_OK) {
+ for (i = 0; i < MAX_INTR_TEST_COUNTER; i ++) {
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != CMD_OK)
+ chdlc_error(card, err, mb);
+ }
+ }
+ else {
+ return err;
+ }
+
+ err = chdlc_set_intr_mode(card, 0);
+
+ if (err != CMD_OK)
+ return err;
+
+ return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. CPIPEAB ?
+ */
+static int udp_pkt_type(struct sk_buff *skb, sdla_t* card)
+{
+ chdlc_udp_pkt_t *chdlc_udp_pkt = (chdlc_udp_pkt_t *)skb->data;
+
+#ifdef _WAN_UDP_DEBUG
+ printk(KERN_INFO "SIG %s = %s\n\
+ UPP %x = %x\n\
+ PRT %x = %x\n\
+ REQ %i = %i\n\
+ 36 th = %x 37th = %x\n",
+ chdlc_udp_pkt->wp_mgmt.signature,
+ UDPMGMT_SIGNATURE,
+ chdlc_udp_pkt->udp_pkt.udp_dst_port,
+ ntohs(card->wandev.udp_port),
+ chdlc_udp_pkt->ip_pkt.protocol,
+ UDPMGMT_UDP_PROTOCOL,
+ chdlc_udp_pkt->wp_mgmt.request_reply,
+ UDPMGMT_REQUEST,
+ skb->data[36], skb->data[37]);
+#endif
+
+ if (!strncmp(chdlc_udp_pkt->wp_mgmt.signature,UDPMGMT_SIGNATURE,8) &&
+ (chdlc_udp_pkt->udp_pkt.udp_dst_port == ntohs(card->wandev.udp_port)) &&
+ (chdlc_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+ (chdlc_udp_pkt->wp_mgmt.request_reply == UDPMGMT_REQUEST)) {
+
+ return UDP_CPIPE_TYPE;
+
+ }else{
+ return UDP_INVALID_TYPE;
+ }
+}
+
+/*============================================================================
+ * Set PORT state.
+ */
+static void port_set_state (sdla_t *card, int state)
+{
+ if (card->u.c.state != state)
+ {
+ switch (state)
+ {
+ case WAN_CONNECTED:
+ printk (KERN_INFO "%s: Link connected!\n",
+ card->devname);
+ break;
+
+ case WAN_CONNECTING:
+ printk (KERN_INFO "%s: Link connecting...\n",
+ card->devname);
+ break;
+
+ case WAN_DISCONNECTED:
+ printk (KERN_INFO "%s: Link disconnected!\n",
+ card->devname);
+ break;
+ }
+
+ card->wandev.state = card->u.c.state = state;
+ if (card->wandev.dev){
+ struct net_device *dev = card->wandev.dev;
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+ chdlc_priv_area->common.state = state;
+ }
+ }
+}
+
+/*===========================================================================
+ * config_chdlc
+ *
+ * Configure the chdlc protocol and enable communications.
+ *
+ * The if_open() function binds this function to the poll routine.
+ * Therefore, this function will run every time the chdlc interface
+ * is brought up. We cannot run this function from the if_open
+ * because if_open does not have access to the remote IP address.
+ *
+ * If the communications are not enabled, proceed to configure
+ * the card and enable communications.
+ *
+ * If the communications are enabled, it means that the interface
+ * was shutdown by ether the user or driver. In this case, we
+ * have to check that the IP addresses have not changed. If
+ * the IP addresses have changed, we have to reconfigure the firmware
+ * and update the changed IP addresses. Otherwise, just exit.
+ *
+ */
+
+static int config_chdlc (sdla_t *card)
+{
+ struct net_device *dev = card->wandev.dev;
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+ if (card->u.c.comm_enabled){
+
+ /* Jun 20. 2000: NC
+ * IP addresses are not used in the API mode */
+
+ if ((chdlc_priv_area->ip_local_tmp != chdlc_priv_area->ip_local ||
+ chdlc_priv_area->ip_remote_tmp != chdlc_priv_area->ip_remote) &&
+ card->u.c.usedby == WANPIPE) {
+
+ /* The IP addersses have changed, we must
+ * stop the communications and reconfigure
+ * the card. Reason: the firmware must know
+ * the local and remote IP addresses. */
+ disable_comm(card);
+ port_set_state(card, WAN_DISCONNECTED);
+ printk(KERN_INFO
+ "%s: IP addresses changed!\n",
+ card->devname);
+ printk(KERN_INFO
+ "%s: Restarting communications ...\n",
+ card->devname);
+ }else{
+ /* IP addresses are the same and the link is up,
+ * we don't have to do anything here. Therefore, exit */
+ return 0;
+ }
+ }
+
+ chdlc_priv_area->ip_local = chdlc_priv_area->ip_local_tmp;
+ chdlc_priv_area->ip_remote = chdlc_priv_area->ip_remote_tmp;
+
+
+ /* Setup the Board for asynchronous mode */
+ if (card->u.c.async_mode){
+
+ if (set_asy_config(card)) {
+ printk (KERN_INFO "%s: Failed CHDLC Async configuration!\n",
+ card->devname);
+ return 0;
+ }
+ }else{
+ /* Setup the Board for CHDLC */
+ if (set_chdlc_config(card)) {
+ printk (KERN_INFO "%s: Failed CHDLC configuration!\n",
+ card->devname);
+ return 0;
+ }
+ }
+
+ /* Set interrupt mode and mask */
+ if (chdlc_set_intr_mode(card, APP_INT_ON_RX_FRAME |
+ APP_INT_ON_GLOBAL_EXCEP_COND |
+ APP_INT_ON_TX_FRAME |
+ APP_INT_ON_CHDLC_EXCEP_COND | APP_INT_ON_TIMER)){
+ printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+ card->devname);
+ return 0;
+ }
+
+
+ /* Mask the Transmit and Timer interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~(APP_INT_ON_TX_FRAME | APP_INT_ON_TIMER);
+
+ /* In TTY mode, receive interrupt will be enabled during
+ * wanpipe_tty_open() operation */
+ if (card->tty_opt){
+ flags->interrupt_info_struct.interrupt_permission &= ~APP_INT_ON_RX_FRAME;
+ }
+
+ /* Enable communications */
+ if (card->u.c.async_mode){
+ if (asy_comm_enable(card) != 0) {
+ printk(KERN_INFO "%s: Failed to enable async commnunication!\n",
+ card->devname);
+ flags->interrupt_info_struct.interrupt_permission = 0;
+ card->u.c.comm_enabled=0;
+ chdlc_set_intr_mode(card,0);
+ return 0;
+ }
+ }else{
+ if (chdlc_comm_enable(card) != 0) {
+ printk(KERN_INFO "%s: Failed to enable chdlc communications!\n",
+ card->devname);
+ flags->interrupt_info_struct.interrupt_permission = 0;
+ card->u.c.comm_enabled=0;
+ chdlc_set_intr_mode(card,0);
+ return 0;
+ }
+ }
+
+ /* Initialize Rx/Tx buffer control fields */
+ init_chdlc_tx_rx_buff(card);
+ port_set_state(card, WAN_CONNECTING);
+ return 0;
+}
+
+
+/*============================================================
+ * chdlc_poll
+ *
+ * Rationale:
+ * We cannot manipulate the routing tables, or
+ * ip addresses withing the interrupt. Therefore
+ * we must perform such actons outside an interrupt
+ * at a later time.
+ *
+ * Description:
+ * CHDLC polling routine, responsible for
+ * shutting down interfaces upon disconnect
+ * and adding/removing routes.
+ *
+ * Usage:
+ * This function is executed for each CHDLC
+ * interface through a tq_schedule bottom half.
+ *
+ * trigger_chdlc_poll() function is used to kick
+ * the chldc_poll routine.
+ */
+
+static void chdlc_poll(struct net_device *dev)
+{
+ chdlc_private_area_t *chdlc_priv_area;
+ sdla_t *card;
+ u8 check_gateway=0;
+ SHARED_MEMORY_INFO_STRUCT* flags;
+
+
+ if (!dev || (chdlc_priv_area=dev->priv) == NULL)
+ return;
+
+ card = chdlc_priv_area->card;
+ flags = card->u.c.flags;
+
+ /* (Re)Configuraiton is in progress, stop what you are
+ * doing and get out */
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ return;
+ }
+
+ /* if_open() function has triggered the polling routine
+ * to determine the configured IP addresses. Once the
+ * addresses are found, trigger the chdlc configuration */
+ if (test_bit(0,&chdlc_priv_area->config_chdlc)){
+
+ chdlc_priv_area->ip_local_tmp = get_ip_address(dev,WAN_LOCAL_IP);
+ chdlc_priv_area->ip_remote_tmp = get_ip_address(dev,WAN_POINTOPOINT_IP);
+
+ /* Jun 20. 2000 Bug Fix
+ * Only perform this check in WANPIPE mode, since
+ * IP addresses are not used in the API mode. */
+
+ if (chdlc_priv_area->ip_local_tmp == chdlc_priv_area->ip_remote_tmp &&
+ card->u.c.slarp_timer == 0x00 &&
+ !card->u.c.backup &&
+ card->u.c.usedby == WANPIPE){
+
+ if (++chdlc_priv_area->ip_error > MAX_IP_ERRORS){
+ printk(KERN_INFO "\n%s: --- WARNING ---\n",
+ card->devname);
+ printk(KERN_INFO
+ "%s: The local IP address is the same as the\n",
+ card->devname);
+ printk(KERN_INFO
+ "%s: Point-to-Point IP address.\n",
+ card->devname);
+ printk(KERN_INFO "%s: --- WARNING ---\n\n",
+ card->devname);
+ }else{
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ chdlc_priv_area->poll_delay_timer.expires = jiffies+HZ;
+ add_timer(&chdlc_priv_area->poll_delay_timer);
+ return;
+ }
+ }
+
+ clear_bit(0,&chdlc_priv_area->config_chdlc);
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+
+ chdlc_priv_area->timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+ flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+ return;
+ }
+ /* Dynamic interface implementation, as well as dynamic
+ * routing. */
+
+ switch (card->u.c.state){
+
+ case WAN_DISCONNECTED:
+
+ /* If the dynamic interface configuration is on, and interface
+ * is up, then bring down the netowrk interface */
+
+ if (test_bit(DYN_OPT_ON,&chdlc_priv_area->interface_down) &&
+ !test_bit(DEV_DOWN, &chdlc_priv_area->interface_down) &&
+ card->wandev.dev->flags & IFF_UP){
+
+ printk(KERN_INFO "%s: Interface %s down.\n",
+ card->devname,card->wandev.dev->name);
+ change_dev_flags(card->wandev.dev,(card->wandev.dev->flags&~IFF_UP));
+ set_bit(DEV_DOWN,&chdlc_priv_area->interface_down);
+ chdlc_priv_area->route_status = NO_ROUTE;
+
+ }else{
+ /* We need to check if the local IP address is
+ * zero. If it is, we shouldn't try to remove it.
+ */
+
+ if (card->wandev.dev->flags & IFF_UP &&
+ get_ip_address(card->wandev.dev,WAN_LOCAL_IP) &&
+ chdlc_priv_area->route_status != NO_ROUTE &&
+ card->u.c.slarp_timer){
+
+ process_route(card);
+ }
+ }
+ break;
+
+ case WAN_CONNECTED:
+
+ /* In SMP machine this code can execute before the interface
+ * comes up. In this case, we must make sure that we do not
+ * try to bring up the interface before dev_open() is finished */
+
+
+ /* DEV_DOWN will be set only when we bring down the interface
+ * for the very first time. This way we know that it was us
+ * that brought the interface down */
+
+ if (test_bit(DYN_OPT_ON,&chdlc_priv_area->interface_down) &&
+ test_bit(DEV_DOWN, &chdlc_priv_area->interface_down) &&
+ !(card->wandev.dev->flags & IFF_UP)){
+
+ printk(KERN_INFO "%s: Interface %s up.\n",
+ card->devname,card->wandev.dev->name);
+ change_dev_flags(card->wandev.dev,(card->wandev.dev->flags|IFF_UP));
+ clear_bit(DEV_DOWN,&chdlc_priv_area->interface_down);
+ check_gateway=1;
+ }
+
+ if (chdlc_priv_area->route_status == ADD_ROUTE &&
+ card->u.c.slarp_timer){
+
+ process_route(card);
+ check_gateway=1;
+ }
+
+ if (chdlc_priv_area->gateway && check_gateway)
+ add_gateway(card,dev);
+
+ break;
+ }
+
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+}
+
+/*============================================================
+ * trigger_chdlc_poll
+ *
+ * Description:
+ * Add a chdlc_poll() work entry into the keventd work queue
+ * for a specific dlci/interface. This will kick
+ * the fr_poll() routine at a later time.
+ *
+ * Usage:
+ * Interrupts use this to defer a taks to
+ * a polling routine.
+ *
+ */
+static void trigger_chdlc_poll(struct net_device *dev)
+{
+ chdlc_private_area_t *chdlc_priv_area;
+ sdla_t *card;
+
+ if (!dev)
+ return;
+
+ if ((chdlc_priv_area = dev->priv)==NULL)
+ return;
+
+ card = chdlc_priv_area->card;
+
+ if (test_and_set_bit(POLL_CRIT,&card->wandev.critical)){
+ return;
+ }
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ return;
+ }
+ schedule_work(&chdlc_priv_area->poll_work);
+}
+
+
+static void chdlc_poll_delay (unsigned long dev_ptr)
+{
+ struct net_device *dev = (struct net_device *)dev_ptr;
+ trigger_chdlc_poll(dev);
+}
+
+
+void s508_lock (sdla_t *card, unsigned long *smp_flags)
+{
+ spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+ if (card->next){
+ spin_lock(&card->next->wandev.lock);
+ }
+}
+
+void s508_unlock (sdla_t *card, unsigned long *smp_flags)
+{
+ if (card->next){
+ spin_unlock(&card->next->wandev.lock);
+ }
+ spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+//*********** TTY SECTION ****************
+
+static void wanpipe_tty_trigger_tx_irq(sdla_t *card)
+{
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ INTERRUPT_INFORMATION_STRUCT *chdlc_int = &flags->interrupt_info_struct;
+ chdlc_int->interrupt_permission |= APP_INT_ON_TX_FRAME;
+}
+
+static void wanpipe_tty_trigger_poll(sdla_t *card)
+{
+ schedule_work(&card->tty_work);
+}
+
+static void tty_poll_work (void* data)
+{
+ sdla_t *card = (sdla_t*)data;
+ struct tty_struct *tty;
+
+ if ((tty=card->tty)==NULL)
+ return;
+
+ tty_wakeup(tty);
+#if defined(SERIAL_HAVE_POLL_WAIT)
+ wake_up_interruptible(&tty->poll_wait);
+#endif
+ return;
+}
+
+static void wanpipe_tty_close(struct tty_struct *tty, struct file * filp)
+{
+ sdla_t *card;
+ unsigned long smp_flags;
+
+ if (!tty || !tty->driver_data){
+ return;
+ }
+
+ card = (sdla_t*)tty->driver_data;
+
+ if (!card)
+ return;
+
+ printk(KERN_INFO "%s: Closing TTY Driver!\n",
+ card->devname);
+
+ /* Sanity Check */
+ if (!card->tty_open)
+ return;
+
+ wanpipe_close(card);
+ if (--card->tty_open == 0){
+
+ lock_adapter_irq(&card->wandev.lock,&smp_flags);
+ card->tty=NULL;
+ chdlc_disable_comm_shutdown(card);
+ unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+
+ if (card->tty_buf){
+ kfree(card->tty_buf);
+ card->tty_buf=NULL;
+ }
+
+ if (card->tty_rx){
+ kfree(card->tty_rx);
+ card->tty_rx=NULL;
+ }
+ }
+ return;
+}
+static int wanpipe_tty_open(struct tty_struct *tty, struct file * filp)
+{
+ unsigned long smp_flags;
+ sdla_t *card;
+
+ if (!tty){
+ return -ENODEV;
+ }
+
+ if (!tty->driver_data){
+ int port;
+ port = tty->index;
+ if ((port < 0) || (port >= NR_PORTS))
+ return -ENODEV;
+
+ tty->driver_data = WAN_CARD(port);
+ if (!tty->driver_data)
+ return -ENODEV;
+ }
+
+ card = (sdla_t*)tty->driver_data;
+
+ if (!card){
+ lock_adapter_irq(&card->wandev.lock,&smp_flags);
+ card->tty=NULL;
+ unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+ return -ENODEV;
+ }
+
+ printk(KERN_INFO "%s: Opening TTY Driver!\n",
+ card->devname);
+
+ if (card->tty_open == 0){
+ lock_adapter_irq(&card->wandev.lock,&smp_flags);
+ card->tty=tty;
+ unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+
+ if (!card->tty_buf){
+ card->tty_buf = kmalloc(TTY_CHDLC_MAX_MTU, GFP_KERNEL);
+ if (!card->tty_buf){
+ card->tty_buf=NULL;
+ card->tty=NULL;
+ return -ENOMEM;
+ }
+ }
+
+ if (!card->tty_rx){
+ card->tty_rx = kmalloc(TTY_CHDLC_MAX_MTU, GFP_KERNEL);
+ if (!card->tty_rx){
+ /* Free the buffer above */
+ kfree(card->tty_buf);
+ card->tty_buf=NULL;
+ card->tty=NULL;
+ return -ENOMEM;
+ }
+ }
+ }
+
+ ++card->tty_open;
+ wanpipe_open(card);
+ return 0;
+}
+
+static int wanpipe_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+ unsigned long smp_flags=0;
+ sdla_t *card=NULL;
+
+ if (!tty){
+ dbg_printk(KERN_INFO "NO TTY in Write\n");
+ return -ENODEV;
+ }
+
+ card = (sdla_t *)tty->driver_data;
+
+ if (!card){
+ dbg_printk(KERN_INFO "No Card in TTY Write\n");
+ return -ENODEV;
+ }
+
+ if (count > card->wandev.mtu){
+ dbg_printk(KERN_INFO "Frame too big in Write %i Max: %i\n",
+ count,card->wandev.mtu);
+ return -EINVAL;
+ }
+
+ if (card->wandev.state != WAN_CONNECTED){
+ dbg_printk(KERN_INFO "Card not connected in TTY Write\n");
+ return -EINVAL;
+ }
+
+ /* Lock the 508 Card: SMP is supported */
+ if(card->hw.type != SDLA_S514){
+ s508_lock(card,&smp_flags);
+ }
+
+ if (test_and_set_bit(SEND_CRIT,(void*)&card->wandev.critical)){
+ printk(KERN_INFO "%s: Critical in TTY Write\n",
+ card->devname);
+
+ /* Lock the 508 Card: SMP is supported */
+ if(card->hw.type != SDLA_S514)
+ s508_unlock(card,&smp_flags);
+
+ return -EINVAL;
+ }
+
+ if (chdlc_send(card,(void*)buf,count)){
+ dbg_printk(KERN_INFO "%s: Failed to send, retry later: kernel!\n",
+ card->devname);
+ clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+
+ wanpipe_tty_trigger_tx_irq(card);
+
+ if(card->hw.type != SDLA_S514)
+ s508_unlock(card,&smp_flags);
+ return 0;
+ }
+ dbg_printk(KERN_INFO "%s: Packet sent OK: %i\n",card->devname,count);
+ clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+
+ if(card->hw.type != SDLA_S514)
+ s508_unlock(card,&smp_flags);
+
+ return count;
+}
+
+static void wanpipe_tty_receive(sdla_t *card, unsigned addr, unsigned int len)
+{
+ unsigned offset=0;
+ unsigned olen=len;
+ char fp=0;
+ struct tty_struct *tty;
+ int i;
+ struct tty_ldisc *ld;
+
+ if (!card->tty_open){
+ dbg_printk(KERN_INFO "%s: TTY not open during receive\n",
+ card->devname);
+ return;
+ }
+
+ if ((tty=card->tty) == NULL){
+ dbg_printk(KERN_INFO "%s: No TTY on receive\n",
+ card->devname);
+ return;
+ }
+
+ if (!tty->driver_data){
+ dbg_printk(KERN_INFO "%s: No Driver Data, or Flip on receive\n",
+ card->devname);
+ return;
+ }
+
+
+ if (card->u.c.async_mode){
+ if ((tty->flip.count+len) >= TTY_FLIPBUF_SIZE){
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Received packet size too big: %i bytes, Max: %i!\n",
+ card->devname,len,TTY_FLIPBUF_SIZE);
+ }
+ return;
+ }
+
+
+ if((addr + len) > card->u.c.rx_top + 1) {
+ offset = card->u.c.rx_top - addr + 1;
+
+ sdla_peek(&card->hw, addr, tty->flip.char_buf_ptr, offset);
+
+ addr = card->u.c.rx_base;
+ len -= offset;
+
+ tty->flip.char_buf_ptr+=offset;
+ tty->flip.count+=offset;
+ for (i=0;i<offset;i++){
+ *tty->flip.flag_buf_ptr = 0;
+ tty->flip.flag_buf_ptr++;
+ }
+ }
+
+ sdla_peek(&card->hw, addr, tty->flip.char_buf_ptr, len);
+
+ tty->flip.char_buf_ptr+=len;
+ card->tty->flip.count+=len;
+ for (i=0;i<len;i++){
+ *tty->flip.flag_buf_ptr = 0;
+ tty->flip.flag_buf_ptr++;
+ }
+
+ tty->low_latency=1;
+ tty_flip_buffer_push(tty);
+ }else{
+ if (!card->tty_rx){
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Receive sync buffer not available!\n",
+ card->devname);
+ }
+ return;
+ }
+
+ if (len > TTY_CHDLC_MAX_MTU){
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Received packet size too big: %i bytes, Max: %i!\n",
+ card->devname,len,TTY_FLIPBUF_SIZE);
+ }
+ return;
+ }
+
+
+ if((addr + len) > card->u.c.rx_top + 1) {
+ offset = card->u.c.rx_top - addr + 1;
+
+ sdla_peek(&card->hw, addr, card->tty_rx, offset);
+
+ addr = card->u.c.rx_base;
+ len -= offset;
+ }
+ sdla_peek(&card->hw, addr, card->tty_rx+offset, len);
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->receive_buf)
+ ld->receive_buf(tty,card->tty_rx,&fp,olen);
+ tty_ldisc_deref(ld);
+ }else{
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: NO TTY Sync line discipline!\n",
+ card->devname);
+ }
+ }
+ }
+
+ dbg_printk(KERN_INFO "%s: Received Data %i\n",card->devname,olen);
+ return;
+}
+
+#if 0
+static int wanpipe_tty_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ return -ENOIOCTLCMD;
+}
+#endif
+
+static void wanpipe_tty_stop(struct tty_struct *tty)
+{
+ return;
+}
+
+static void wanpipe_tty_start(struct tty_struct *tty)
+{
+ return;
+}
+
+static int config_tty (sdla_t *card)
+{
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+ /* Setup the Board for asynchronous mode */
+ if (card->u.c.async_mode){
+
+ if (set_asy_config(card)) {
+ printk (KERN_INFO "%s: Failed CHDLC Async configuration!\n",
+ card->devname);
+ return -EINVAL;
+ }
+ }else{
+ /* Setup the Board for CHDLC */
+ if (set_chdlc_config(card)) {
+ printk (KERN_INFO "%s: Failed CHDLC configuration!\n",
+ card->devname);
+ return -EINVAL;
+ }
+ }
+
+ /* Set interrupt mode and mask */
+ if (chdlc_set_intr_mode(card, APP_INT_ON_RX_FRAME |
+ APP_INT_ON_GLOBAL_EXCEP_COND |
+ APP_INT_ON_TX_FRAME |
+ APP_INT_ON_CHDLC_EXCEP_COND | APP_INT_ON_TIMER)){
+ printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+ card->devname);
+ return -EINVAL;
+ }
+
+
+ /* Mask the Transmit and Timer interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~(APP_INT_ON_TX_FRAME | APP_INT_ON_TIMER);
+
+
+ /* Enable communications */
+ if (card->u.c.async_mode){
+ if (asy_comm_enable(card) != 0) {
+ printk(KERN_INFO "%s: Failed to enable async commnunication!\n",
+ card->devname);
+ flags->interrupt_info_struct.interrupt_permission = 0;
+ card->u.c.comm_enabled=0;
+ chdlc_set_intr_mode(card,0);
+ return -EINVAL;
+ }
+ }else{
+ if (chdlc_comm_enable(card) != 0) {
+ printk(KERN_INFO "%s: Failed to enable chdlc communications!\n",
+ card->devname);
+ flags->interrupt_info_struct.interrupt_permission = 0;
+ card->u.c.comm_enabled=0;
+ chdlc_set_intr_mode(card,0);
+ return -EINVAL;
+ }
+ }
+
+ /* Initialize Rx/Tx buffer control fields */
+ init_chdlc_tx_rx_buff(card);
+ port_set_state(card, WAN_CONNECTING);
+ return 0;
+}
+
+
+static int change_speed(sdla_t *card, struct tty_struct *tty,
+ struct termios *old_termios)
+{
+ int baud, ret=0;
+ unsigned cflag;
+ int dbits,sbits,parity,handshaking;
+
+ cflag = tty->termios->c_cflag;
+
+ /* There is always one stop bit */
+ sbits=WANOPT_ONE;
+
+ /* Parity is defaulted to NONE */
+ parity = WANOPT_NONE;
+
+ handshaking=0;
+
+ /* byte size and parity */
+ switch (cflag & CSIZE) {
+ case CS5: dbits = 5; break;
+ case CS6: dbits = 6; break;
+ case CS7: dbits = 7; break;
+ case CS8: dbits = 8; break;
+ /* Never happens, but GCC is too dumb to figure it out */
+ default: dbits = 8; break;
+ }
+
+ /* One more stop bit should be supported, thus increment
+ * the number of stop bits Max=2 */
+ if (cflag & CSTOPB) {
+ sbits = WANOPT_TWO;
+ }
+ if (cflag & PARENB) {
+ parity = WANOPT_EVEN;
+ }
+ if (cflag & PARODD){
+ parity = WANOPT_ODD;
+ }
+
+ /* Determine divisor based on baud rate */
+ baud = tty_get_baud_rate(tty);
+
+ if (!baud)
+ baud = 9600; /* B0 transition handled in rs_set_termios */
+
+ if (cflag & CRTSCTS) {
+ handshaking|=ASY_RTS_HS_FOR_RX;
+ }
+
+ if (I_IGNPAR(tty))
+ parity = WANOPT_NONE;
+
+ if (I_IXOFF(tty)){
+ handshaking|=ASY_XON_XOFF_HS_FOR_RX;
+ handshaking|=ASY_XON_XOFF_HS_FOR_TX;
+ }
+
+ if (I_IXON(tty)){
+ handshaking|=ASY_XON_XOFF_HS_FOR_RX;
+ handshaking|=ASY_XON_XOFF_HS_FOR_TX;
+ }
+
+ if (card->u.c.async_mode){
+ if (card->wandev.bps != baud)
+ ret=1;
+ card->wandev.bps = baud;
+ }
+
+ if (card->u.c.async_mode){
+ if (card->u.c.protocol_options != handshaking)
+ ret=1;
+ card->u.c.protocol_options = handshaking;
+
+ if (card->u.c.tx_bits_per_char != dbits)
+ ret=1;
+ card->u.c.tx_bits_per_char = dbits;
+
+ if (card->u.c.rx_bits_per_char != dbits)
+ ret=1;
+ card->u.c.rx_bits_per_char = dbits;
+
+ if (card->u.c.stop_bits != sbits)
+ ret=1;
+ card->u.c.stop_bits = sbits;
+
+ if (card->u.c.parity != parity)
+ ret=1;
+ card->u.c.parity = parity;
+
+ card->u.c.break_timer = 50;
+ card->u.c.inter_char_timer = 10;
+ card->u.c.rx_complete_length = 100;
+ card->u.c.xon_char = 0xFE;
+ }else{
+ card->u.c.protocol_options = HDLC_STREAMING_MODE;
+ }
+
+ return ret;
+}
+
+
+static void wanpipe_tty_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ sdla_t *card;
+ int err=1;
+
+ if (!tty){
+ return;
+ }
+
+ card = (sdla_t *)tty->driver_data;
+
+ if (!card)
+ return;
+
+ if (change_speed(card, tty, old_termios) || !card->u.c.comm_enabled){
+ unsigned long smp_flags;
+
+ if (card->u.c.comm_enabled){
+ lock_adapter_irq(&card->wandev.lock,&smp_flags);
+ chdlc_disable_comm_shutdown(card);
+ unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+ }
+ lock_adapter_irq(&card->wandev.lock,&smp_flags);
+ err = config_tty(card);
+ unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+ if (card->u.c.async_mode){
+ printk(KERN_INFO "%s: TTY Async Configuration:\n"
+ " Baud =%i\n"
+ " Handshaking =%s\n"
+ " Tx Dbits =%i\n"
+ " Rx Dbits =%i\n"
+ " Parity =%s\n"
+ " Stop Bits =%i\n",
+ card->devname,
+ card->wandev.bps,
+ opt_decode[card->u.c.protocol_options],
+ card->u.c.tx_bits_per_char,
+ card->u.c.rx_bits_per_char,
+ p_decode[card->u.c.parity] ,
+ card->u.c.stop_bits);
+ }else{
+ printk(KERN_INFO "%s: TTY Sync Configuration:\n"
+ " Baud =%i\n"
+ " Protocol =HDLC_STREAMING\n",
+ card->devname,card->wandev.bps);
+ }
+ if (!err){
+ port_set_state(card,WAN_CONNECTED);
+ }else{
+ port_set_state(card,WAN_DISCONNECTED);
+ }
+ }
+ return;
+}
+
+static void wanpipe_tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ sdla_t *card;
+ unsigned long smp_flags=0;
+
+ if (!tty){
+ return;
+ }
+
+ card = (sdla_t *)tty->driver_data;
+
+ if (!card)
+ return;
+
+ if (card->wandev.state != WAN_CONNECTED)
+ return;
+
+ if(card->hw.type != SDLA_S514)
+ s508_lock(card,&smp_flags);
+
+ if (test_and_set_bit(SEND_CRIT,(void*)&card->wandev.critical)){
+
+ wanpipe_tty_trigger_tx_irq(card);
+
+ if(card->hw.type != SDLA_S514)
+ s508_unlock(card,&smp_flags);
+ return;
+ }
+
+ if (chdlc_send(card,(void*)&ch,1)){
+ wanpipe_tty_trigger_tx_irq(card);
+ dbg_printk("%s: Failed to TX char!\n",card->devname);
+ }
+
+ dbg_printk("%s: Char TX OK\n",card->devname);
+
+ clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+
+ if(card->hw.type != SDLA_S514)
+ s508_unlock(card,&smp_flags);
+
+ return;
+}
+
+static void wanpipe_tty_flush_chars(struct tty_struct *tty)
+{
+ return;
+}
+
+static void wanpipe_tty_flush_buffer(struct tty_struct *tty)
+{
+ if (!tty)
+ return;
+
+#if defined(SERIAL_HAVE_POLL_WAIT)
+ wake_up_interruptible(&tty->poll_wait);
+#endif
+ tty_wakeup(tty);
+ return;
+}
+
+/*
+ * This function is used to send a high-priority XON/XOFF character to
+ * the device
+ */
+static void wanpipe_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+ return;
+}
+
+
+static int wanpipe_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ return 0;
+}
+
+
+static int wanpipe_tty_write_room(struct tty_struct *tty)
+{
+ sdla_t *card;
+
+ printk(KERN_INFO "TTY Write Room\n");
+
+ if (!tty){
+ return 0;
+ }
+
+ card = (sdla_t *)tty->driver_data;
+ if (!card)
+ return 0;
+
+ if (card->wandev.state != WAN_CONNECTED)
+ return 0;
+
+ return SEC_MAX_NO_DATA_BYTES_IN_FRAME;
+}
+
+
+static int set_modem_status(sdla_t *card, unsigned char data)
+{
+ CHDLC_MAILBOX_STRUCT *mb = card->mbox;
+ int err;
+
+ mb->buffer_length=1;
+ mb->command=SET_MODEM_STATUS;
+ mb->data[0]=data;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error (card, err, mb);
+
+ return err;
+}
+
+static void wanpipe_tty_hangup(struct tty_struct *tty)
+{
+ sdla_t *card;
+ unsigned long smp_flags;
+
+ printk(KERN_INFO "TTY Hangup!\n");
+
+ if (!tty){
+ return;
+ }
+
+ card = (sdla_t *)tty->driver_data;
+ if (!card)
+ return;
+
+ lock_adapter_irq(&card->wandev.lock,&smp_flags);
+ set_modem_status(card,0);
+ unlock_adapter_irq(&card->wandev.lock,&smp_flags);
+ return;
+}
+
+static void wanpipe_tty_break(struct tty_struct *tty, int break_state)
+{
+ return;
+}
+
+static void wanpipe_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ return;
+}
+
+static void wanpipe_tty_throttle(struct tty_struct * tty)
+{
+ return;
+}
+
+static void wanpipe_tty_unthrottle(struct tty_struct * tty)
+{
+ return;
+}
+
+int wanpipe_tty_read_proc(char *page, char **start, off_t off, int count,
+ int *eof, void *data)
+{
+ return 0;
+}
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+int wanpipe_tty_init(sdla_t *card)
+{
+ struct serial_state * state;
+
+ /* Initialize the tty_driver structure */
+
+ if (card->tty_minor < 0 || card->tty_minor > NR_PORTS){
+ printk(KERN_INFO "%s: Illegal Minor TTY number (0-4): %i\n",
+ card->devname,card->tty_minor);
+ return -EINVAL;
+ }
+
+ if (WAN_CARD(card->tty_minor)){
+ printk(KERN_INFO "%s: TTY Minor %i, already in use\n",
+ card->devname,card->tty_minor);
+ return -EBUSY;
+ }
+
+ if (tty_init_cnt==0){
+
+ printk(KERN_INFO "%s: TTY %s Driver Init: Major %i, Minor Range %i-%i\n",
+ card->devname,
+ card->u.c.async_mode ? "ASYNC" : "SYNC",
+ WAN_TTY_MAJOR,MIN_PORT,MAX_PORT);
+
+ tty_driver_mode = card->u.c.async_mode;
+
+ memset(&serial_driver, 0, sizeof(struct tty_driver));
+ serial_driver.magic = TTY_DRIVER_MAGIC;
+ serial_driver.owner = THIS_MODULE;
+ serial_driver.driver_name = "wanpipe_tty";
+ serial_driver.name = "ttyW";
+ serial_driver.major = WAN_TTY_MAJOR;
+ serial_driver.minor_start = WAN_TTY_MINOR;
+ serial_driver.num = NR_PORTS;
+ serial_driver.type = TTY_DRIVER_TYPE_SERIAL;
+ serial_driver.subtype = SERIAL_TYPE_NORMAL;
+
+ serial_driver.init_termios = tty_std_termios;
+ serial_driver.init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ serial_driver.flags = TTY_DRIVER_REAL_RAW;
+
+ serial_driver.refcount = 1; /* !@!@^#^&!! */
+
+ serial_driver.open = wanpipe_tty_open;
+ serial_driver.close = wanpipe_tty_close;
+ serial_driver.write = wanpipe_tty_write;
+
+ serial_driver.put_char = wanpipe_tty_put_char;
+ serial_driver.flush_chars = wanpipe_tty_flush_chars;
+ serial_driver.write_room = wanpipe_tty_write_room;
+ serial_driver.chars_in_buffer = wanpipe_tty_chars_in_buffer;
+ serial_driver.flush_buffer = wanpipe_tty_flush_buffer;
+ //serial_driver.ioctl = wanpipe_tty_ioctl;
+ serial_driver.throttle = wanpipe_tty_throttle;
+ serial_driver.unthrottle = wanpipe_tty_unthrottle;
+ serial_driver.send_xchar = wanpipe_tty_send_xchar;
+ serial_driver.set_termios = wanpipe_tty_set_termios;
+ serial_driver.stop = wanpipe_tty_stop;
+ serial_driver.start = wanpipe_tty_start;
+ serial_driver.hangup = wanpipe_tty_hangup;
+ serial_driver.break_ctl = wanpipe_tty_break;
+ serial_driver.wait_until_sent = wanpipe_tty_wait_until_sent;
+ serial_driver.read_proc = wanpipe_tty_read_proc;
+
+ if (tty_register_driver(&serial_driver)){
+ printk(KERN_INFO "%s: Failed to register serial driver!\n",
+ card->devname);
+ }
+ }
+
+
+ /* The subsequent ports must comply to the initial configuration */
+ if (tty_driver_mode != card->u.c.async_mode){
+ printk(KERN_INFO "%s: Error: TTY Driver operation mode mismatch!\n",
+ card->devname);
+ printk(KERN_INFO "%s: The TTY driver is configured for %s!\n",
+ card->devname, tty_driver_mode ? "ASYNC" : "SYNC");
+ return -EINVAL;
+ }
+
+ tty_init_cnt++;
+
+ printk(KERN_INFO "%s: Initializing TTY %s Driver Minor %i\n",
+ card->devname,
+ tty_driver_mode ? "ASYNC" : "SYNC",
+ card->tty_minor);
+
+ tty_card_map[card->tty_minor] = card;
+ state = &rs_table[card->tty_minor];
+
+ state->magic = SSTATE_MAGIC;
+ state->line = 0;
+ state->type = PORT_UNKNOWN;
+ state->custom_divisor = 0;
+ state->close_delay = 5*HZ/10;
+ state->closing_wait = 30*HZ;
+ state->icount.cts = state->icount.dsr =
+ state->icount.rng = state->icount.dcd = 0;
+ state->icount.rx = state->icount.tx = 0;
+ state->icount.frame = state->icount.parity = 0;
+ state->icount.overrun = state->icount.brk = 0;
+ state->irq = card->wandev.irq;
+
+ INIT_WORK(&card->tty_work, tty_poll_work, (void*)card);
+ return 0;
+}
+
+
+MODULE_LICENSE("GPL");
+
+/****** End ****************************************************************/
diff --git a/drivers/net/wan/sdla_fr.c b/drivers/net/wan/sdla_fr.c
new file mode 100644
index 00000000000..2efccb0554c
--- /dev/null
+++ b/drivers/net/wan/sdla_fr.c
@@ -0,0 +1,5068 @@
+/*****************************************************************************
+* sdla_fr.c WANPIPE(tm) Multiprotocol WAN Link Driver. Frame relay module.
+*
+* Author(s): Nenad Corbic <ncorbic@sangoma.com>
+* Gideon Hack
+*
+* Copyright: (c) 1995-2001 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Nov 23, 2000 Nenad Corbic o Added support for 2.4.X kernels
+* Nov 15, 2000 David Rokavarg
+* Nenad Corbic o Added frame relay bridging support.
+* Original code from Mark Wells and Kristian Hoffmann has
+* been integrated into the frame relay driver.
+* Nov 13, 2000 Nenad Corbic o Added true interface type encoding option.
+* Tcpdump doesn't support Frame Relay inteface
+* types, to fix this true type option will set
+* the interface type to RAW IP mode.
+* Nov 07, 2000 Nenad Corbic o Added security features for UDP debugging:
+* Deny all and specify allowed requests.
+* Nov 06, 2000 Nenad Corbic o Wanpipe interfaces conform to raw packet interfaces.
+* Moved the if_header into the if_send() routine.
+* The if_header() was breaking the libpcap
+* support. i.e. support for tcpdump, ethereal ...
+* Oct 12. 2000 Nenad Corbic o Added error message in fr_configure
+* Jul 31, 2000 Nenad Corbic o Fixed the Router UP Time.
+* Apr 28, 2000 Nenad Corbic o Added the option to shutdown an interface
+* when the channel gets disconnected.
+* Apr 28, 2000 Nenad Corbic o Added M.Grants patch: disallow duplicate
+* interface setups.
+* Apr 25, 2000 Nenad Corbic o Added M.Grants patch: dynamically add/remove
+* new dlcis/interfaces.
+* Mar 23, 2000 Nenad Corbic o Improved task queue, bh handling.
+* Mar 16, 2000 Nenad Corbic o Added Inverse ARP support
+* Mar 13, 2000 Nenad Corbic o Added new socket API support.
+* Mar 06, 2000 Nenad Corbic o Bug Fix: corrupted mbox recovery.
+* Feb 24, 2000 Nenad Corbic o Fixed up FT1 UDP debugging problem.
+* Dev 15, 1999 Nenad Corbic o Fixed up header files for 2.0.X kernels
+*
+* Nov 08, 1999 Nenad Corbic o Combined all debug UDP calls into one function
+* o Removed the ARP support. This has to be done
+* in the next version.
+* o Only a Node can implement NO signalling.
+* Initialize DLCI during if_open() if NO
+* signalling.
+* o Took out IPX support, implement in next
+* version
+* Sep 29, 1999 Nenad Corbic o Added SMP support and changed the update
+* function to use timer interrupt.
+* o Fixed the CIR bug: Set the value of BC
+* to CIR when the CIR is enabled.
+* o Updated comments, statistics and tracing.
+* Jun 02, 1999 Gideon Hack o Updated for S514 support.
+* Sep 18, 1998 Jaspreet Singh o Updated for 2.2.X kernels.
+* Jul 31, 1998 Jaspreet Singh o Removed wpf_poll routine. The channel/DLCI
+* status is received through an event interrupt.
+* Jul 08, 1998 David Fong o Added inverse ARP support.
+* Mar 26, 1997 Jaspreet Singh o Returning return codes for failed UDP cmds.
+* Jan 28, 1997 Jaspreet Singh o Improved handling of inactive DLCIs.
+* Dec 30, 1997 Jaspreet Singh o Replaced dev_tint() with mark_bh(NET_BH)
+* Dec 16, 1997 Jaspreet Singh o Implemented Multiple IPX support.
+* Nov 26, 1997 Jaspreet Singh o Improved load sharing with multiple boards
+* o Added Cli() to protect enabling of interrupts
+* while polling is called.
+* Nov 24, 1997 Jaspreet Singh o Added counters to avoid enabling of interrupts
+* when they have been disabled by another
+* interface or routine (eg. wpf_poll).
+* Nov 06, 1997 Jaspreet Singh o Added INTR_TEST_MODE to avoid polling
+* routine disable interrupts during interrupt
+* testing.
+* Oct 20, 1997 Jaspreet Singh o Added hooks in for Router UP time.
+* Oct 16, 1997 Jaspreet Singh o The critical flag is used to maintain flow
+* control by avoiding RACE conditions. The
+* cli() and restore_flags() are taken out.
+* The fr_channel structure is appended for
+* Driver Statistics.
+* Oct 15, 1997 Farhan Thawar o updated if_send() and receive for IPX
+* Aug 29, 1997 Farhan Thawar o Removed most of the cli() and sti()
+* o Abstracted the UDP management stuff
+* o Now use tbusy and critical more intelligently
+* Jul 21, 1997 Jaspreet Singh o Can configure T391, T392, N391, N392 & N393
+* through router.conf.
+* o Protected calls to sdla_peek() by adDing
+* save_flags(), cli() and restore_flags().
+* o Added error message for Inactive DLCIs in
+* fr_event() and update_chan_state().
+* o Fixed freeing up of buffers using kfree()
+* when packets are received.
+* Jul 07, 1997 Jaspreet Singh o Added configurable TTL for UDP packets
+* o Added ability to discard multicast and
+* broadcast source addressed packets
+* Jun 27, 1997 Jaspreet Singh o Added FT1 monitor capabilities
+* New case (0x44) statement in if_send routine
+* Added a global variable rCount to keep track
+* of FT1 status enabled on the board.
+* May 29, 1997 Jaspreet Singh o Fixed major Flow Control Problem
+* With multiple boards a problem was seen where
+* the second board always stopped transmitting
+* packet after running for a while. The code
+* got into a stage where the interrupts were
+* disabled and dev->tbusy was set to 1.
+* This caused the If_send() routine to get into
+* the if clause for it(0,dev->tbusy)
+* forever.
+* The code got into this stage due to an
+* interrupt occurring within the if clause for
+* set_bit(0,dev->tbusy). Since an interrupt
+* disables furhter transmit interrupt and
+* makes dev->tbusy = 0, this effect was undone
+* by making dev->tbusy = 1 in the if clause.
+* The Fix checks to see if Transmit interrupts
+* are disabled then do not make dev->tbusy = 1
+* Introduced a global variable: int_occur and
+* added tx_int_enabled in the wan_device
+* structure.
+* May 21, 1997 Jaspreet Singh o Fixed UDP Management for multiple
+* boards.
+*
+* Apr 25, 1997 Farhan Thawar o added UDP Management stuff
+* o fixed bug in if_send() and tx_intr() to
+* sleep and wakeup all devices
+* Mar 11, 1997 Farhan Thawar Version 3.1.1
+* o fixed (+1) bug in fr508_rx_intr()
+* o changed if_send() to return 0 if
+* wandev.critical() is true
+* o free socket buffer in if_send() if
+* returning 0
+* o added tx_intr() routine
+* Jan 30, 1997 Gene Kozin Version 3.1.0
+* o implemented exec() entry point
+* o fixed a bug causing driver configured as
+* a FR switch to be stuck in WAN_
+* mode
+* Jan 02, 1997 Gene Kozin Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+#include <linux/workqueue.h>
+#include <linux/if_arp.h> /* ARPHRD_* defines */
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <asm/io.h> /* for inb(), outb(), etc. */
+#include <linux/time.h> /* for do_gettimeofday */
+#include <linux/in.h> /* sockaddr_in */
+#include <asm/errno.h>
+
+#include <linux/ip.h>
+#include <linux/if.h>
+
+#include <linux/if_wanpipe_common.h> /* Wanpipe Socket */
+#include <linux/if_wanpipe.h>
+
+#include <linux/sdla_fr.h> /* frame relay firmware API definitions */
+
+#include <asm/uaccess.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+#include <net/route.h> /* Dynamic Route Creation */
+#include <linux/etherdevice.h> /* eth_type_trans() used for bridging */
+#include <linux/random.h>
+
+/****** Defines & Macros ****************************************************/
+
+#define MAX_CMD_RETRY 10 /* max number of firmware retries */
+
+#define FR_HEADER_LEN 8 /* max encapsulation header size */
+#define FR_CHANNEL_MTU 1500 /* unfragmented logical channel MTU */
+
+/* Q.922 frame types */
+#define Q922_UI 0x03 /* Unnumbered Info frame */
+#define Q922_XID 0xAF
+
+/* DLCI configured or not */
+#define DLCI_NOT_CONFIGURED 0x00
+#define DLCI_CONFIG_PENDING 0x01
+#define DLCI_CONFIGURED 0x02
+
+/* CIR enabled or not */
+#define CIR_ENABLED 0x00
+#define CIR_DISABLED 0x01
+
+#define FRAME_RELAY_API 1
+#define MAX_BH_BUFF 10
+
+/* For handle_IPXWAN() */
+#define CVHexToAscii(b) (((unsigned char)(b) > (unsigned char)9) ? ((unsigned char)'A' + ((unsigned char)(b) - (unsigned char)10)) : ((unsigned char)'0' + (unsigned char)(b)))
+
+/****** Data Structures *****************************************************/
+
+/* This is an extention of the 'struct device' we create for each network
+ * interface to keep the rest of channel-specific data.
+ */
+typedef struct fr_channel
+{
+ wanpipe_common_t common;
+ char name[WAN_IFNAME_SZ+1]; /* interface name, ASCIIZ */
+ unsigned dlci_configured ; /* check whether configured or not */
+ unsigned cir_status; /* check whether CIR enabled or not */
+ unsigned dlci; /* logical channel number */
+ unsigned cir; /* committed information rate */
+ unsigned bc; /* committed burst size */
+ unsigned be; /* excess burst size */
+ unsigned mc; /* multicast support on or off */
+ unsigned tx_int_status; /* Transmit Interrupt Status */
+ unsigned short pkt_length; /* Packet Length */
+ unsigned long router_start_time;/* Router start time in seconds */
+ unsigned long tick_counter; /* counter for transmit time out */
+ char dev_pending_devtint; /* interface pending dev_tint() */
+ void *dlci_int_interface; /* pointer to the DLCI Interface */
+ unsigned long IB_addr; /* physical address of Interface Byte */
+ unsigned long state_tick; /* time of the last state change */
+ unsigned char enable_IPX; /* Enable/Disable the use of IPX */
+ unsigned long network_number; /* Internal Network Number for IPX*/
+ sdla_t *card; /* -> owner */
+ unsigned route_flag; /* Add/Rem dest addr in route tables */
+ unsigned inarp; /* Inverse Arp Request status */
+ long inarp_ready; /* Ready to send requests */
+ int inarp_interval; /* Time between InArp Requests */
+ unsigned long inarp_tick; /* InArp jiffies tick counter */
+ long interface_down; /* Bring interface down on disconnect */
+ struct net_device_stats ifstats; /* interface statistics */
+ if_send_stat_t drvstats_if_send;
+ rx_intr_stat_t drvstats_rx_intr;
+ pipe_mgmt_stat_t drvstats_gen;
+ unsigned long router_up_time;
+
+ unsigned short transmit_length;
+ struct sk_buff *delay_skb;
+
+ bh_data_t *bh_head; /* Circular buffer for chdlc_bh */
+ unsigned long tq_working;
+ volatile int bh_write;
+ volatile int bh_read;
+ atomic_t bh_buff_used;
+
+ /* Polling task queue. Each interface
+ * has its own task queue, which is used
+ * to defer events from the interrupt */
+ struct work_struct fr_poll_work;
+ struct timer_list fr_arp_timer;
+
+ u32 ip_local;
+ u32 ip_remote;
+ long config_dlci;
+ long unconfig_dlci;
+
+ /* Whether this interface should be setup as a gateway.
+ * Used by dynamic route setup code */
+ u8 gateway;
+
+ /* True interface type */
+ u8 true_if_encoding;
+ u8 fr_header[FR_HEADER_LEN];
+ char fr_header_len;
+
+} fr_channel_t;
+
+/* Route Flag options */
+#define NO_ROUTE 0x00
+#define ADD_ROUTE 0x01
+#define ROUTE_ADDED 0x02
+#define REMOVE_ROUTE 0x03
+#define ARP_REQ 0x04
+
+/* inarp options */
+#define INARP_NONE 0x00
+#define INARP_REQUEST 0x01
+#define INARP_CONFIGURED 0x02
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP 0x01
+#define TMR_INT_ENABLED_UPDATE 0x02
+#define TMR_INT_ENABLED_ARP 0x04
+#define TMR_INT_ENABLED_UPDATE_STATE 0x08
+#define TMR_INT_ENABLED_CONFIG 0x10
+#define TMR_INT_ENABLED_UNCONFIG 0x20
+
+
+typedef struct dlci_status
+{
+ unsigned short dlci PACKED;
+ unsigned char state PACKED;
+} dlci_status_t;
+
+typedef struct dlci_IB_mapping
+{
+ unsigned short dlci PACKED;
+ unsigned long addr_value PACKED;
+} dlci_IB_mapping_t;
+
+/* This structure is used for DLCI list Tx interrupt mode. It is used to
+ enable interrupt bit and set the packet length for transmission
+ */
+typedef struct fr_dlci_interface
+{
+ unsigned char gen_interrupt PACKED;
+ unsigned short packet_length PACKED;
+ unsigned char reserved PACKED;
+} fr_dlci_interface_t;
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/* variable for keeping track of number of interrupts generated during
+ * interrupt test routine
+ */
+static int Intr_test_counter;
+
+/****** Function Prototypes *************************************************/
+
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device *wandev);
+static int new_if(struct wan_device *wandev, struct net_device *dev,
+ wanif_conf_t *conf);
+static int del_if(struct wan_device *wandev, struct net_device *dev);
+static void disable_comm (sdla_t *card);
+
+/* WANPIPE-specific entry points */
+static int wpf_exec(struct sdla *card, void *u_cmd, void *u_data);
+
+/* Network device interface */
+static int if_init(struct net_device *dev);
+static int if_open(struct net_device *dev);
+static int if_close(struct net_device *dev);
+
+static void if_tx_timeout(struct net_device *dev);
+
+static int if_rebuild_hdr (struct sk_buff *skb);
+
+static int if_send(struct sk_buff *skb, struct net_device *dev);
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+ struct sk_buff *skb);
+static struct net_device_stats *if_stats(struct net_device *dev);
+
+/* Interrupt handlers */
+static void fr_isr(sdla_t *card);
+static void rx_intr(sdla_t *card);
+static void tx_intr(sdla_t *card);
+static void timer_intr(sdla_t *card);
+static void spur_intr(sdla_t *card);
+
+/* Frame relay firmware interface functions */
+static int fr_read_version(sdla_t *card, char *str);
+static int fr_configure(sdla_t *card, fr_conf_t *conf);
+static int fr_dlci_configure(sdla_t *card, fr_dlc_conf_t *conf, unsigned dlci);
+static int fr_init_dlci (sdla_t *card, fr_channel_t *chan);
+static int fr_set_intr_mode (sdla_t *card, unsigned mode, unsigned mtu, unsigned short timeout);
+static int fr_comm_enable(sdla_t *card);
+static void fr_comm_disable(sdla_t *card);
+static int fr_get_err_stats(sdla_t *card);
+static int fr_get_stats(sdla_t *card);
+static int fr_add_dlci(sdla_t *card, int dlci);
+static int fr_activate_dlci(sdla_t *card, int dlci);
+static int fr_delete_dlci (sdla_t* card, int dlci);
+static int fr_issue_isf(sdla_t *card, int isf);
+static int fr_send(sdla_t *card, int dlci, unsigned char attr, int len,
+ void *buf);
+static int fr_send_data_header(sdla_t *card, int dlci, unsigned char attr, int len,
+ void *buf,unsigned char hdr_len);
+static unsigned int fr_send_hdr(sdla_t *card, int dlci, unsigned int offset);
+
+static int check_dlci_config (sdla_t *card, fr_channel_t *chan);
+static void initialize_rx_tx_buffers (sdla_t *card);
+
+
+/* Firmware asynchronous event handlers */
+static int fr_event(sdla_t *card, int event, fr_mbox_t *mbox);
+static int fr_modem_failure(sdla_t *card, fr_mbox_t *mbox);
+static int fr_dlci_change(sdla_t *card, fr_mbox_t *mbox);
+
+/* Miscellaneous functions */
+static int update_chan_state(struct net_device *dev);
+static void set_chan_state(struct net_device *dev, int state);
+static struct net_device *find_channel(sdla_t *card, unsigned dlci);
+static int is_tx_ready(sdla_t *card, fr_channel_t *chan);
+static unsigned int dec_to_uint(unsigned char *str, int len);
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+
+static int intr_test( sdla_t* card );
+static void init_chan_statistics( fr_channel_t* chan );
+static void init_global_statistics( sdla_t* card );
+static void read_DLCI_IB_mapping( sdla_t* card, fr_channel_t* chan );
+static int setup_for_delayed_transmit(struct net_device* dev,
+ struct sk_buff *skb);
+
+struct net_device *move_dev_to_next(sdla_t *card, struct net_device *dev);
+static int check_tx_status(sdla_t *card, struct net_device *dev);
+
+/* Frame Relay Socket API */
+static void trigger_fr_bh (fr_channel_t *);
+static void fr_bh(struct net_device *dev);
+static int fr_bh_cleanup(struct net_device *dev);
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb);
+
+static void trigger_fr_poll(struct net_device *dev);
+static void fr_poll(struct net_device *dev);
+//static void add_gateway(struct net_device *dev);
+
+static void trigger_unconfig_fr(struct net_device *dev);
+static void unconfig_fr (sdla_t *);
+
+static void trigger_config_fr (sdla_t *);
+static void config_fr (sdla_t *);
+
+
+/* Inverse ARP and Dynamic routing functions */
+int process_ARP(arphdr_1490_t *ArpPacket, sdla_t *card, struct net_device *dev);
+int is_arp(void *buf);
+int send_inarp_request(sdla_t *card, struct net_device *dev);
+
+static void trigger_fr_arp(struct net_device *dev);
+static void fr_arp (unsigned long data);
+
+
+/* Udp management functions */
+static int process_udp_mgmt_pkt(sdla_t *card);
+static int udp_pkt_type( struct sk_buff *skb, sdla_t *card );
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, int dlci);
+
+/* IPX functions */
+static void switch_net_numbers(unsigned char *sendpacket,
+ unsigned long network_number, unsigned char incoming);
+
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname,
+ unsigned char enable_IPX, unsigned long network_number);
+
+/* Lock Functions: SMP supported */
+void s508_s514_unlock(sdla_t *card, unsigned long *smp_flags);
+void s508_s514_lock(sdla_t *card, unsigned long *smp_flags);
+
+unsigned short calc_checksum (char *, int);
+static int setup_fr_header(struct sk_buff** skb,
+ struct net_device* dev, char op_mode);
+
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Frame relay protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup. At this
+ * point adapter is completely initialized and firmware is running.
+ * o read firmware version (to make sure it's alive)
+ * o configure adapter
+ * o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return: 0 o.k.
+ * < 0 failure.
+ */
+int wpf_init(sdla_t *card, wandev_conf_t *conf)
+{
+
+ int err;
+ fr508_flags_t* flags;
+
+ union
+ {
+ char str[80];
+ fr_conf_t cfg;
+ } u;
+
+ fr_buf_info_t* buf_info;
+ int i;
+
+
+ printk(KERN_INFO "\n");
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_FR) {
+
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id);
+ return -EINVAL;
+
+ }
+
+ /* Initialize protocol-specific fields of adapter data space */
+ switch (card->hw.fwid) {
+
+ case SFID_FR508:
+ card->mbox = (void*)(card->hw.dpmbase +
+ FR508_MBOX_OFFS);
+ card->flags = (void*)(card->hw.dpmbase +
+ FR508_FLAG_OFFS);
+ if(card->hw.type == SDLA_S514) {
+ card->mbox += FR_MB_VECTOR;
+ card->flags += FR_MB_VECTOR;
+ }
+ card->isr = &fr_isr;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ flags = card->flags;
+
+ /* Read firmware version. Note that when adapter initializes, it
+ * clears the mailbox, so it may appear that the first command was
+ * executed successfully when in fact it was merely erased. To work
+ * around this, we execute the first command twice.
+ */
+
+ if (fr_read_version(card, NULL) || fr_read_version(card, u.str))
+ return -EIO;
+
+ printk(KERN_INFO "%s: running frame relay firmware v%s\n",
+ card->devname, u.str);
+
+ /* Adjust configuration */
+ conf->mtu += FR_HEADER_LEN;
+ conf->mtu = (conf->mtu >= MIN_LGTH_FR_DATA_CFG) ?
+ min_t(unsigned int, conf->mtu, FR_MAX_NO_DATA_BYTES_IN_FRAME) :
+ FR_CHANNEL_MTU + FR_HEADER_LEN;
+
+ conf->bps = min_t(unsigned int, conf->bps, 2048000);
+
+ /* Initialze the configuration structure sent to the board to zero */
+ memset(&u.cfg, 0, sizeof(u.cfg));
+
+ memset(card->u.f.dlci_to_dev_map, 0, sizeof(card->u.f.dlci_to_dev_map));
+
+ /* Configure adapter firmware */
+
+ u.cfg.mtu = conf->mtu;
+ u.cfg.kbps = conf->bps / 1000;
+
+ u.cfg.cir_fwd = u.cfg.cir_bwd = 16;
+ u.cfg.bc_fwd = u.cfg.bc_bwd = 16;
+
+ u.cfg.options = 0x0000;
+ printk(KERN_INFO "%s: Global CIR enabled by Default\n", card->devname);
+
+ switch (conf->u.fr.signalling) {
+
+ case WANOPT_FR_ANSI:
+ u.cfg.options = 0x0000;
+ break;
+
+ case WANOPT_FR_Q933:
+ u.cfg.options |= 0x0200;
+ break;
+
+ case WANOPT_FR_LMI:
+ u.cfg.options |= 0x0400;
+ break;
+
+ case WANOPT_NO:
+ u.cfg.options |= 0x0800;
+ break;
+ default:
+ printk(KERN_INFO "%s: Illegal Signalling option\n",
+ card->wandev.name);
+ return -EINVAL;
+ }
+
+
+ card->wandev.signalling = conf->u.fr.signalling;
+
+ if (conf->station == WANOPT_CPE) {
+
+
+ if (conf->u.fr.signalling == WANOPT_NO){
+ printk(KERN_INFO
+ "%s: ERROR - For NO signalling, station must be set to Node!",
+ card->devname);
+ return -EINVAL;
+ }
+
+ u.cfg.station = 0;
+ u.cfg.options |= 0x8000; /* auto config DLCI */
+ card->u.f.dlci_num = 0;
+
+ } else {
+
+ u.cfg.station = 1; /* switch emulation mode */
+
+ /* For switch emulation we have to create a list of dlci(s)
+ * that will be sent to be global SET_DLCI_CONFIGURATION
+ * command in fr_configure() routine.
+ */
+
+ card->u.f.dlci_num = min_t(unsigned int, max_t(unsigned int, conf->u.fr.dlci_num, 1), 100);
+
+ for ( i = 0; i < card->u.f.dlci_num; i++) {
+
+ card->u.f.node_dlci[i] = (unsigned short)
+ conf->u.fr.dlci[i] ? conf->u.fr.dlci[i] : 16;
+
+ }
+ }
+
+ if (conf->clocking == WANOPT_INTERNAL)
+ u.cfg.port |= 0x0001;
+
+ if (conf->interface == WANOPT_RS232)
+ u.cfg.port |= 0x0002;
+
+ if (conf->u.fr.t391)
+ u.cfg.t391 = min_t(unsigned int, conf->u.fr.t391, 30);
+ else
+ u.cfg.t391 = 5;
+
+ if (conf->u.fr.t392)
+ u.cfg.t392 = min_t(unsigned int, conf->u.fr.t392, 30);
+ else
+ u.cfg.t392 = 15;
+
+ if (conf->u.fr.n391)
+ u.cfg.n391 = min_t(unsigned int, conf->u.fr.n391, 255);
+ else
+ u.cfg.n391 = 2;
+
+ if (conf->u.fr.n392)
+ u.cfg.n392 = min_t(unsigned int, conf->u.fr.n392, 10);
+ else
+ u.cfg.n392 = 3;
+
+ if (conf->u.fr.n393)
+ u.cfg.n393 = min_t(unsigned int, conf->u.fr.n393, 10);
+ else
+ u.cfg.n393 = 4;
+
+ if (fr_configure(card, &u.cfg))
+ return -EIO;
+
+ if (card->hw.type == SDLA_S514) {
+
+ buf_info = (void*)(card->hw.dpmbase + FR_MB_VECTOR +
+ FR508_RXBC_OFFS);
+
+ card->rxmb = (void*)(buf_info->rse_next + card->hw.dpmbase);
+
+ card->u.f.rxmb_base =
+ (void*)(buf_info->rse_base + card->hw.dpmbase);
+
+ card->u.f.rxmb_last =
+ (void*)(buf_info->rse_base +
+ (buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) +
+ card->hw.dpmbase);
+ }else{
+ buf_info = (void*)(card->hw.dpmbase + FR508_RXBC_OFFS);
+
+ card->rxmb = (void*)(buf_info->rse_next -
+ FR_MB_VECTOR + card->hw.dpmbase);
+
+ card->u.f.rxmb_base =
+ (void*)(buf_info->rse_base -
+ FR_MB_VECTOR + card->hw.dpmbase);
+
+ card->u.f.rxmb_last =
+ (void*)(buf_info->rse_base +
+ (buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) -
+ FR_MB_VECTOR + card->hw.dpmbase);
+ }
+
+ card->u.f.rx_base = buf_info->buf_base;
+ card->u.f.rx_top = buf_info->buf_top;
+
+ card->u.f.tx_interrupts_pending = 0;
+
+ card->wandev.mtu = conf->mtu;
+ card->wandev.bps = conf->bps;
+ card->wandev.interface = conf->interface;
+ card->wandev.clocking = conf->clocking;
+ card->wandev.station = conf->station;
+ card->poll = NULL;
+ card->exec = &wpf_exec;
+ card->wandev.update = &update;
+ card->wandev.new_if = &new_if;
+ card->wandev.del_if = &del_if;
+ card->wandev.state = WAN_DISCONNECTED;
+ card->wandev.ttl = conf->ttl;
+ card->wandev.udp_port = conf->udp_port;
+ card->disable_comm = &disable_comm;
+ card->u.f.arp_dev = NULL;
+
+ /* Intialize global statistics for a card */
+ init_global_statistics( card );
+
+ card->TracingEnabled = 0;
+
+ /* Interrupt Test */
+ Intr_test_counter = 0;
+ card->intr_mode = INTR_TEST_MODE;
+ err = intr_test( card );
+
+ printk(KERN_INFO "%s: End of Interrupt Test rc=0x%x count=%i\n",
+ card->devname,err,Intr_test_counter);
+
+ if (err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) {
+ printk(KERN_ERR "%s: Interrupt Test Failed, Counter: %i\n",
+ card->devname, Intr_test_counter);
+ printk(KERN_ERR "Please choose another interrupt\n");
+ err = -EIO;
+ return err;
+ }
+
+ printk(KERN_INFO "%s: Interrupt Test Passed, Counter: %i\n",
+ card->devname, Intr_test_counter);
+
+
+ /* Apr 28 2000. Nenad Corbic
+ * Enable commnunications here, not in if_open or new_if, since
+ * interfaces come down when the link is disconnected.
+ */
+
+ /* If you enable comms and then set ints, you get a Tx int as you
+ * perform the SET_INT_TRIGGERS command. So, we only set int
+ * triggers and then adjust the interrupt mask (to disable Tx ints)
+ * before enabling comms.
+ */
+ if (fr_set_intr_mode(card, (FR_INTR_RXRDY | FR_INTR_TXRDY |
+ FR_INTR_DLC | FR_INTR_TIMER | FR_INTR_TX_MULT_DLCIs) ,
+ card->wandev.mtu, 0)) {
+ return -EIO;
+ }
+
+ flags->imask &= ~(FR_INTR_TXRDY | FR_INTR_TIMER);
+
+ if (fr_comm_enable(card)) {
+ return -EIO;
+ }
+ wanpipe_set_state(card, WAN_CONNECTED);
+ spin_lock_init(&card->u.f.if_send_lock);
+
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics.
+ */
+static int update(struct wan_device* wandev)
+{
+ volatile sdla_t* card;
+ unsigned long timeout;
+ fr508_flags_t* flags;
+
+ /* sanity checks */
+ if ((wandev == NULL) || (wandev->private == NULL))
+ return -EFAULT;
+
+ if (wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ card = wandev->private;
+ flags = card->flags;
+
+
+ card->u.f.update_comms_stats = 1;
+ card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UPDATE;
+ flags->imask |= FR_INTR_TIMER;
+ timeout = jiffies;
+ for(;;) {
+ if(card->u.f.update_comms_stats == 0)
+ break;
+ if ((jiffies - timeout) > (1 * HZ)){
+ card->u.f.update_comms_stats = 0;
+ return -EAGAIN;
+ }
+ }
+
+ return 0;
+}
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return: 0 o.k.
+ * < 0 failure (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+ wanif_conf_t* conf)
+{
+ sdla_t* card = wandev->private;
+ fr_channel_t* chan;
+ int dlci = 0;
+ int err = 0;
+
+
+ if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+
+ printk(KERN_INFO "%s: Invalid interface name!\n",
+ card->devname);
+ return -EINVAL;
+ }
+
+ /* allocate and initialize private data */
+ chan = kmalloc(sizeof(fr_channel_t), GFP_KERNEL);
+
+ if (chan == NULL)
+ return -ENOMEM;
+
+ memset(chan, 0, sizeof(fr_channel_t));
+ strcpy(chan->name, conf->name);
+ chan->card = card;
+
+ /* verify media address */
+ if (is_digit(conf->addr[0])) {
+
+ dlci = dec_to_uint(conf->addr, 0);
+
+ if (dlci && (dlci <= HIGHEST_VALID_DLCI)) {
+
+ chan->dlci = dlci;
+
+ } else {
+
+ printk(KERN_ERR
+ "%s: Invalid DLCI %u on interface %s!\n",
+ wandev->name, dlci, chan->name);
+ err = -EINVAL;
+ }
+
+ } else {
+ printk(KERN_ERR
+ "%s: Invalid media address on interface %s!\n",
+ wandev->name, chan->name);
+ err = -EINVAL;
+ }
+
+ if ((chan->true_if_encoding = conf->true_if_encoding) == WANOPT_YES){
+ printk(KERN_INFO
+ "%s: Enabling, true interface type encoding.\n",
+ card->devname);
+ }
+
+
+
+ /* Setup wanpipe as a router (WANPIPE) even if it is
+ * a bridged DLCI, or as an API
+ */
+ if (strcmp(conf->usedby, "WANPIPE") == 0 ||
+ strcmp(conf->usedby, "BRIDGE") == 0 ||
+ strcmp(conf->usedby, "BRIDGE_N") == 0){
+
+ if(strcmp(conf->usedby, "WANPIPE") == 0){
+ chan->common.usedby = WANPIPE;
+
+ printk(KERN_INFO "%s: Running in WANPIPE mode.\n",
+ card->devname);
+
+ }else if(strcmp(conf->usedby, "BRIDGE") == 0){
+
+ chan->common.usedby = BRIDGE;
+
+ printk(KERN_INFO "%s: Running in WANPIPE (BRIDGE) mode.\n",
+ card->devname);
+ }else if( strcmp(conf->usedby, "BRIDGE_N") == 0 ){
+
+ chan->common.usedby = BRIDGE_NODE;
+
+ printk(KERN_INFO "%s: Running in WANPIPE (BRIDGE_NODE) mode.\n",
+ card->devname);
+ }
+
+ if (!err){
+ /* Dynamic interface configuration option.
+ * On disconnect, if the options is selected,
+ * the interface will be brought down */
+ if (conf->if_down == WANOPT_YES){
+ set_bit(DYN_OPT_ON,&chan->interface_down);
+ printk(KERN_INFO
+ "%s: Dynamic interface configuration enabled.\n",
+ card->devname);
+ }
+ }
+
+ } else if(strcmp(conf->usedby, "API") == 0){
+
+ chan->common.usedby = API;
+ printk(KERN_INFO "%s: Running in API mode.\n",
+ wandev->name);
+ }
+
+ if (err) {
+
+ kfree(chan);
+ return err;
+ }
+
+ /* place cir,be,bc and other channel specific information into the
+ * chan structure
+ */
+ if (conf->cir) {
+
+ chan->cir = max_t(unsigned int, 1,
+ min_t(unsigned int, conf->cir, 512));
+ chan->cir_status = CIR_ENABLED;
+
+
+ /* If CIR is enabled, force BC to equal CIR
+ * this solves number of potential problems if CIR is
+ * set and BC is not
+ */
+ chan->bc = chan->cir;
+
+ if (conf->be){
+ chan->be = max_t(unsigned int,
+ 0, min_t(unsigned int, conf->be, 511));
+ }else{
+ conf->be = 0;
+ }
+
+ printk (KERN_INFO "%s: CIR enabled for DLCI %i \n",
+ wandev->name,chan->dlci);
+ printk (KERN_INFO "%s: CIR = %i ; BC = %i ; BE = %i\n",
+ wandev->name,chan->cir,chan->bc,chan->be);
+
+
+ }else{
+ chan->cir_status = CIR_DISABLED;
+ printk (KERN_INFO "%s: CIR disabled for DLCI %i\n",
+ wandev->name,chan->dlci);
+ }
+
+ chan->mc = conf->mc;
+
+ if (conf->inarp == WANOPT_YES){
+ printk(KERN_INFO "%s: Inverse ARP Support Enabled\n",card->devname);
+ chan->inarp = conf->inarp ? INARP_REQUEST : INARP_NONE;
+ chan->inarp_interval = conf->inarp_interval ? conf->inarp_interval : 10;
+ }else{
+ printk(KERN_INFO "%s: Inverse ARP Support Disabled\n",card->devname);
+ chan->inarp = INARP_NONE;
+ chan->inarp_interval = 10;
+ }
+
+
+ chan->dlci_configured = DLCI_NOT_CONFIGURED;
+
+
+ /*FIXME: IPX disabled in this WANPIPE version */
+ if (conf->enable_IPX == WANOPT_YES){
+ printk(KERN_INFO "%s: ERROR - This version of WANPIPE doesn't support IPX\n",
+ card->devname);
+ kfree(chan);
+ return -EINVAL;
+ }else{
+ chan->enable_IPX = WANOPT_NO;
+ }
+
+ if (conf->network_number){
+ chan->network_number = conf->network_number;
+ }else{
+ chan->network_number = 0xDEADBEEF;
+ }
+
+ chan->route_flag = NO_ROUTE;
+
+ init_chan_statistics(chan);
+
+ chan->transmit_length = 0;
+
+ /* prepare network device data space for registration */
+ strcpy(dev->name,chan->name);
+
+ dev->init = &if_init;
+ dev->priv = chan;
+
+ /* Initialize FR Polling Task Queue
+ * We need a poll routine for each network
+ * interface.
+ */
+ INIT_WORK(&chan->fr_poll_work, (void *)fr_poll, dev);
+
+ init_timer(&chan->fr_arp_timer);
+ chan->fr_arp_timer.data=(unsigned long)dev;
+ chan->fr_arp_timer.function = fr_arp;
+
+ wandev->new_if_cnt++;
+
+ /* Tells us that if this interface is a
+ * gateway or not */
+ if ((chan->gateway = conf->gateway) == WANOPT_YES){
+ printk(KERN_INFO "%s: Interface %s is set as a gateway.\n",
+ card->devname,dev->name);
+ }
+
+ /* M. Grant Patch Apr 28 2000
+ * Disallow duplicate dlci configurations. */
+ if (card->u.f.dlci_to_dev_map[chan->dlci] != NULL) {
+ kfree(chan);
+ return -EBUSY;
+ }
+
+ /* Configure this dlci at a later date, when
+ * the interface comes up. i.e. when if_open()
+ * executes */
+ set_bit(0,&chan->config_dlci);
+
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+/*============================================================================
+ * Delete logical channel.
+ */
+static int del_if(struct wan_device* wandev, struct net_device* dev)
+{
+ fr_channel_t* chan = dev->priv;
+ unsigned long smp_flags=0;
+
+ /* This interface is dead, make sure the
+ * ARP timer is stopped */
+ del_timer(&chan->fr_arp_timer);
+
+ /* If we are a NODE, we must unconfigure this DLCI
+ * Trigger an unconfigure command that will
+ * be executed in timer interrupt. We must wait
+ * for the command to complete. */
+ trigger_unconfig_fr(dev);
+
+ lock_adapter_irq(&wandev->lock, &smp_flags);
+ wandev->new_if_cnt--;
+ unlock_adapter_irq(&wandev->lock, &smp_flags);
+
+ return 0;
+}
+
+
+/*=====================================================================
+ * disable_comm
+ *
+ * Description:
+ * Disable communications.
+ * This code runs in shutdown (sdlamain.c)
+ * under critical flag. Therefore it is not
+ * necessary to set a critical flag here
+ *
+ * Usage:
+ * Commnunications are disabled only on a card
+ * shutdown.
+ */
+
+static void disable_comm (sdla_t *card)
+{
+ printk(KERN_INFO "%s: Disabling Communications!\n",
+ card->devname);
+ fr_comm_disable(card);
+}
+
+/****** WANPIPE-specific entry points ***************************************/
+
+/*============================================================================
+ * Execute adapter interface command.
+ */
+static int wpf_exec (struct sdla* card, void* u_cmd, void* u_data)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err, len;
+ fr_cmd_t cmd;
+
+ if(copy_from_user((void*)&cmd, u_cmd, sizeof(cmd)))
+ return -EFAULT;
+
+ /* execute command */
+ do
+ {
+ memcpy(&mbox->cmd, &cmd, sizeof(cmd));
+
+ if (cmd.length){
+ if( copy_from_user((void*)&mbox->data, u_data, cmd.length))
+ return -EFAULT;
+ }
+
+ if (sdla_exec(mbox))
+ err = mbox->cmd.result;
+
+ else return -EIO;
+
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ /* return result */
+ if (copy_to_user(u_cmd, (void*)&mbox->cmd, sizeof(fr_cmd_t)))
+ return -EFAULT;
+
+ len = mbox->cmd.length;
+
+ if (len && u_data && !copy_to_user(u_data, (void*)&mbox->data, len))
+ return -EFAULT;
+ return 0;
+}
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration. Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device* dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ struct wan_device* wandev = &card->wandev;
+
+ /* Initialize device driver entry points */
+ dev->open = &if_open;
+ dev->stop = &if_close;
+ dev->hard_header = NULL;
+ dev->rebuild_header = &if_rebuild_hdr;
+ dev->hard_start_xmit = &if_send;
+ dev->get_stats = &if_stats;
+ dev->tx_timeout = &if_tx_timeout;
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+ if (chan->common.usedby == WANPIPE || chan->common.usedby == API){
+
+ /* Initialize media-specific parameters */
+ if (chan->true_if_encoding){
+ dev->type = ARPHRD_DLCI; /* This breaks tcpdump */
+ }else{
+ dev->type = ARPHRD_PPP; /* ARP h/w type */
+ }
+
+ dev->flags |= IFF_POINTOPOINT;
+ dev->flags |= IFF_NOARP;
+
+ /* Enable Multicast addressing */
+ if (chan->mc == WANOPT_YES){
+ dev->flags |= IFF_MULTICAST;
+ }
+
+ dev->mtu = wandev->mtu - FR_HEADER_LEN;
+ /* For an API, the maximum number of bytes that the stack will pass
+ to the driver is (dev->mtu + dev->hard_header_len). So, adjust the
+ mtu so that a frame of maximum size can be transmitted by the API.
+ */
+ if(chan->common.usedby == API) {
+ dev->mtu += (sizeof(api_tx_hdr_t) - FR_HEADER_LEN);
+ }
+
+ dev->hard_header_len = FR_HEADER_LEN;/* media header length */
+ dev->addr_len = 2; /* hardware address length */
+ *(unsigned short*)dev->dev_addr = htons(chan->dlci);
+
+ /* Set transmit buffer queue length */
+ dev->tx_queue_len = 100;
+
+ }else{
+
+ /* Setup the interface for Bridging */
+ int hw_addr=0;
+ ether_setup(dev);
+
+ /* Use a random number to generate the MAC address */
+ memcpy(dev->dev_addr, "\xFE\xFC\x00\x00\x00\x00", 6);
+ get_random_bytes(&hw_addr, sizeof(hw_addr));
+ *(int *)(dev->dev_addr + 2) += hw_addr;
+ }
+
+ /* Initialize hardware parameters (just for reference) */
+ dev->irq = wandev->irq;
+ dev->dma = wandev->dma;
+ dev->base_addr = wandev->ioport;
+ dev->mem_start = wandev->maddr;
+ dev->mem_end = wandev->maddr + wandev->msize - 1;
+ SET_MODULE_OWNER(dev);
+
+ return 0;
+}
+
+/*============================================================================
+ * Open network interface.
+ * o if this is the first open, then enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device* dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ int err = 0;
+ struct timeval tv;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ /* Initialize the task queue */
+ chan->tq_working=0;
+
+ INIT_WORK(&chan->common.wanpipe_work, (void *)fr_bh, dev);
+
+ /* Allocate and initialize BH circular buffer */
+ chan->bh_head = kmalloc((sizeof(bh_data_t)*MAX_BH_BUFF),GFP_ATOMIC);
+ memset(chan->bh_head,0,(sizeof(bh_data_t)*MAX_BH_BUFF));
+ atomic_set(&chan->bh_buff_used, 0);
+
+ netif_start_queue(dev);
+
+ wanpipe_open(card);
+ do_gettimeofday( &tv );
+ chan->router_start_time = tv.tv_sec;
+
+ if (test_bit(0,&chan->config_dlci)){
+ trigger_config_fr (card);
+ }else if (chan->inarp == INARP_REQUEST){
+ trigger_fr_arp(dev);
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last open, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device* dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+
+ if (chan->inarp == INARP_CONFIGURED) {
+ chan->inarp = INARP_REQUEST;
+ }
+
+ netif_stop_queue(dev);
+ wanpipe_close(card);
+
+ return 0;
+}
+
+/*============================================================================
+ * Re-build media header.
+ *
+ * Return: 1 physical address resolved.
+ * 0 physical address not resolved
+ */
+static int if_rebuild_hdr (struct sk_buff* skb)
+{
+ struct net_device *dev = skb->dev;
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+
+ printk(KERN_INFO "%s: rebuild_header() called for interface %s!\n",
+ card->devname, dev->name);
+ return 1;
+}
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ /* If our device stays busy for at least 5 seconds then we will
+ * kick start the device by making dev->tbusy = 0. We expect
+ * that our device never stays busy more than 5 seconds. So this
+ * is only used as a last resort.
+ */
+
+ chan->drvstats_if_send.if_send_tbusy++;
+ ++chan->ifstats.collisions;
+
+ printk (KERN_INFO "%s: Transmit timed out on %s\n",
+ card->devname, dev->name);
+ chan->drvstats_if_send.if_send_tbusy_timeout++;
+ netif_wake_queue (dev);
+
+}
+
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ * transmit from overlapping.
+ * o set critical flag when accessing board.
+ * o check link state. If link is not up, then drop the packet.
+ * o check channel status. If it's down then initiate a call.
+ * o pass a packet to corresponding WAN device.
+ * o free socket buffer
+ *
+ * Return: 0 complete (socket buffer must be freed)
+ * non-0 packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ * bottom half" (with interrupts enabled).
+ *
+ * 2. Using netif_start_queue() and netif_stop_queue()
+ * will inhibit further transmit requests from the protocol stack
+ * and can be used for flow control with protocol layer.
+ */
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ int err;
+ unsigned char *sendpacket;
+ fr508_flags_t* adptr_flags = card->flags;
+ int udp_type;
+ long delay_tx_queued = 0;
+ unsigned long smp_flags=0;
+ unsigned char attr = 0;
+
+ chan->drvstats_if_send.if_send_entry++;
+
+ netif_stop_queue(dev);
+
+ if (skb == NULL) {
+ /* if we get here, some higher layer thinks we've missed an
+ * tx-done interrupt.
+ */
+ printk(KERN_INFO "%s: interface %s got kicked!\n",
+ card->devname, dev->name);
+ chan->drvstats_if_send.if_send_skb_null ++;
+
+ netif_wake_queue(dev);
+ return 0;
+ }
+
+ /* If a peripheral task is running just drop packets */
+ if (test_bit(PERI_CRIT, &card->wandev.critical)){
+
+ printk(KERN_INFO "%s: Critical in if_send(): Peripheral running!\n",
+ card->devname);
+
+ dev_kfree_skb_any(skb);
+ netif_start_queue(dev);
+ return 0;
+ }
+
+ /* We must set the 'tbusy' flag if we already have a packet queued for
+ transmission in the transmit interrupt handler. However, we must
+ ensure that the transmit interrupt does not reset the 'tbusy' flag
+ just before we set it, as this will result in a "transmit timeout".
+ */
+ set_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical);
+ if(chan->transmit_length) {
+ netif_stop_queue(dev);
+ chan->tick_counter = jiffies;
+ clear_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical);
+ return 1;
+ }
+ clear_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical);
+
+ /* Move the if_header() code to here. By inserting frame
+ * relay header in if_header() we would break the
+ * tcpdump and other packet sniffers */
+ chan->fr_header_len = setup_fr_header(&skb,dev,chan->common.usedby);
+ if (chan->fr_header_len < 0 ){
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+
+ dev_kfree_skb_any(skb);
+ netif_start_queue(dev);
+ return 0;
+ }
+
+ sendpacket = skb->data;
+
+ udp_type = udp_pkt_type(skb, card);
+
+ if(udp_type != UDP_INVALID_TYPE) {
+ if(store_udp_mgmt_pkt(udp_type, UDP_PKT_FRM_STACK, card, skb,
+ chan->dlci)) {
+ adptr_flags->imask |= FR_INTR_TIMER;
+ if (udp_type == UDP_FPIPE_TYPE){
+ chan->drvstats_if_send.
+ if_send_PIPE_request ++;
+ }
+ }
+ netif_start_queue(dev);
+ return 0;
+ }
+
+ //FIXME: can we do better than sendpacket[2]?
+ if ((chan->common.usedby == WANPIPE) && (sendpacket[2] == 0x45)) {
+
+ /* check to see if the source IP address is a broadcast or */
+ /* multicast IP address */
+ if(chk_bcast_mcast_addr(card, dev, skb)){
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ dev_kfree_skb_any(skb);
+ netif_start_queue(dev);
+ return 0;
+ }
+ }
+
+
+ /* Lock the S514/S508 card: SMP Supported */
+ s508_s514_lock(card,&smp_flags);
+
+ if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+
+ chan->drvstats_if_send.if_send_critical_non_ISR ++;
+ chan->ifstats.tx_dropped ++;
+ printk(KERN_INFO "%s Critical in IF_SEND: if_send() already running!\n",
+ card->devname);
+ goto if_send_start_and_exit;
+ }
+
+ /* API packet check: minimum packet size must be greater than
+ * 16 byte API header */
+ if((chan->common.usedby == API) && (skb->len <= sizeof(api_tx_hdr_t))) {
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+
+
+ goto if_send_start_and_exit;
+
+ }else{
+ /* During API transmission, get rid of the API header */
+ if (chan->common.usedby == API) {
+ api_tx_hdr_t* api_tx_hdr;
+ api_tx_hdr = (api_tx_hdr_t*)&skb->data[0x00];
+ attr = api_tx_hdr->attr;
+ skb_pull(skb,sizeof(api_tx_hdr_t));
+ }
+ }
+
+ if (card->wandev.state != WAN_CONNECTED) {
+ chan->drvstats_if_send.if_send_wan_disconnected ++;
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+
+ } else if (chan->common.state != WAN_CONNECTED) {
+ chan->drvstats_if_send.if_send_dlci_disconnected ++;
+
+ /* Update the DLCI state in timer interrupt */
+ card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UPDATE_STATE;
+ adptr_flags->imask |= FR_INTR_TIMER;
+
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+
+ } else if (!is_tx_ready(card, chan)) {
+ /* No tx buffers available, store for delayed transmit */
+ if (!setup_for_delayed_transmit(dev, skb)){
+ set_bit(1,&delay_tx_queued);
+ }
+ chan->drvstats_if_send.if_send_no_bfrs++;
+
+ } else if (!skb->protocol) {
+ /* No protocols drop packet */
+ chan->drvstats_if_send.if_send_protocol_error ++;
+ ++card->wandev.stats.tx_errors;
+
+ } else if (test_bit(ARP_CRIT,&card->wandev.critical)){
+ /* We are trying to send an ARP Packet, block IP data until
+ * ARP is sent */
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+
+ } else {
+ //FIXME: IPX is not implemented in this version of Frame Relay ?
+ if((chan->common.usedby == WANPIPE) &&
+ sendpacket[1] == 0x00 &&
+ sendpacket[2] == 0x80 &&
+ sendpacket[6] == 0x81 &&
+ sendpacket[7] == 0x37) {
+
+ if( chan->enable_IPX ) {
+ switch_net_numbers(sendpacket,
+ chan->network_number, 0);
+ } else {
+ //FIXME: Take this out when IPX is fixed
+ printk(KERN_INFO
+ "%s: WARNING: Unsupported IPX data in send, packet dropped\n",
+ card->devname);
+ }
+
+ }else{
+ err = fr_send_data_header(card, chan->dlci, attr, skb->len, skb->data, chan->fr_header_len);
+ if (err) {
+ switch(err) {
+ case FRRES_CIR_OVERFLOW:
+ case FRRES_BUFFER_OVERFLOW:
+ if (!setup_for_delayed_transmit(dev, skb)){
+ set_bit(1,&delay_tx_queued);
+ }
+ chan->drvstats_if_send.
+ if_send_adptr_bfrs_full ++;
+ break;
+
+ case FRRES_TOO_LONG:
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Error: Frame too long, transmission failed %i\n",
+ card->devname, (unsigned int)skb->len);
+ }
+ /* Drop down to default */
+ default:
+ chan->drvstats_if_send.
+ if_send_dlci_disconnected ++;
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ break;
+ }
+ } else {
+ chan->drvstats_if_send.
+ if_send_bfr_passed_to_adptr++;
+ ++chan->ifstats.tx_packets;
+ ++card->wandev.stats.tx_packets;
+
+ chan->ifstats.tx_bytes += skb->len;
+ card->wandev.stats.tx_bytes += skb->len;
+ dev->trans_start = jiffies;
+ }
+ }
+ }
+
+if_send_start_and_exit:
+
+ netif_start_queue(dev);
+
+ /* If we queued the packet for transmission, we must not
+ * deallocate it. The packet is unlinked from the IP stack
+ * not copied. Therefore, we must keep the original packet */
+ if (!test_bit(1,&delay_tx_queued)) {
+ dev_kfree_skb_any(skb);
+ }else{
+ adptr_flags->imask |= FR_INTR_TXRDY;
+ card->u.f.tx_interrupts_pending ++;
+ }
+
+ clear_bit(SEND_CRIT, (void*)&card->wandev.critical);
+
+ s508_s514_unlock(card,&smp_flags);
+
+ return 0;
+}
+
+
+
+/*============================================================================
+ * Setup so that a frame can be transmitted on the occurrence of a transmit
+ * interrupt.
+ */
+static int setup_for_delayed_transmit(struct net_device* dev,
+ struct sk_buff *skb)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ fr_dlci_interface_t* dlci_interface;
+ int len = skb->len;
+
+ /* Check that the dlci is properly configured,
+ * before using tx interrupt */
+ if (!chan->dlci_int_interface){
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: ERROR on DLCI %i: Not configured properly !\n",
+ card->devname, chan->dlci);
+ printk(KERN_INFO "%s: Please contact Sangoma Technologies\n",
+ card->devname);
+ }
+ return 1;
+ }
+
+ dlci_interface = chan->dlci_int_interface;
+
+ if(chan->transmit_length) {
+ printk(KERN_INFO "%s: Big mess in setup_for_del...\n",
+ card->devname);
+ return 1;
+ }
+
+ if(len > FR_MAX_NO_DATA_BYTES_IN_FRAME) {
+ //FIXME: increment some statistic */
+ return 1;
+ }
+
+ skb_unlink(skb);
+
+ chan->transmit_length = len;
+ chan->delay_skb = skb;
+
+ dlci_interface->gen_interrupt |= FR_INTR_TXRDY;
+ dlci_interface->packet_length = len;
+
+ /* Turn on TX interrupt at the end of if_send */
+ return 0;
+}
+
+
+/*============================================================================
+ * Check to see if the packet to be transmitted contains a broadcast or
+ * multicast source IP address.
+ * Return 0 if not broadcast/multicast address, otherwise return 1.
+ */
+
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+ struct sk_buff *skb)
+{
+ u32 src_ip_addr;
+ u32 broadcast_ip_addr = 0;
+ struct in_device *in_dev;
+ fr_channel_t* chan = dev->priv;
+
+ /* read the IP source address from the outgoing packet */
+ src_ip_addr = *(u32 *)(skb->data + 14);
+
+ /* read the IP broadcast address for the device */
+ in_dev = dev->ip_ptr;
+ if(in_dev != NULL) {
+ struct in_ifaddr *ifa= in_dev->ifa_list;
+ if(ifa != NULL)
+ broadcast_ip_addr = ifa->ifa_broadcast;
+ else
+ return 0;
+ }
+
+ /* check if the IP Source Address is a Broadcast address */
+ if((dev->flags & IFF_BROADCAST) && (src_ip_addr == broadcast_ip_addr)) {
+ printk(KERN_INFO
+ "%s: Broadcast Source Address silently discarded\n",
+ card->devname);
+ return 1;
+ }
+
+ /* check if the IP Source Address is a Multicast address */
+ if((chan->mc == WANOPT_NO) && (ntohl(src_ip_addr) >= 0xE0000001) &&
+ (ntohl(src_ip_addr) <= 0xFFFFFFFE)) {
+ printk(KERN_INFO
+ "%s: Multicast Source Address silently discarded\n",
+ card->devname);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return nothing.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+ unsigned short len, udp_length, temp, ip_length;
+ unsigned long ip_temp;
+ int even_bound = 0;
+
+
+ fr_udp_pkt_t *fr_udp_pkt = (fr_udp_pkt_t *)data;
+
+ /* Set length of packet */
+ len = //sizeof(fr_encap_hdr_t)+
+ sizeof(ip_pkt_t)+
+ sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ mbox_len;
+
+
+ /* fill in UDP reply */
+ fr_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+
+ /* fill in UDP length */
+ udp_length = sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ mbox_len;
+
+
+ /* put it on an even boundary */
+ if ( udp_length & 0x0001 ) {
+ udp_length += 1;
+ len += 1;
+ even_bound = 1;
+ }
+
+ temp = (udp_length<<8)|(udp_length>>8);
+ fr_udp_pkt->udp_pkt.udp_length = temp;
+
+ /* swap UDP ports */
+ temp = fr_udp_pkt->udp_pkt.udp_src_port;
+ fr_udp_pkt->udp_pkt.udp_src_port =
+ fr_udp_pkt->udp_pkt.udp_dst_port;
+ fr_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+
+
+ /* add UDP pseudo header */
+ temp = 0x1100;
+ *((unsigned short *)
+ (fr_udp_pkt->data+mbox_len+even_bound)) = temp;
+ temp = (udp_length<<8)|(udp_length>>8);
+ *((unsigned short *)
+ (fr_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+ /* calculate UDP checksum */
+ fr_udp_pkt->udp_pkt.udp_checksum = 0;
+
+ fr_udp_pkt->udp_pkt.udp_checksum =
+ calc_checksum(&data[UDP_OFFSET/*+sizeof(fr_encap_hdr_t)*/],
+ udp_length+UDP_OFFSET);
+
+ /* fill in IP length */
+ ip_length = udp_length + sizeof(ip_pkt_t);
+ temp = (ip_length<<8)|(ip_length>>8);
+ fr_udp_pkt->ip_pkt.total_length = temp;
+
+ /* swap IP addresses */
+ ip_temp = fr_udp_pkt->ip_pkt.ip_src_address;
+ fr_udp_pkt->ip_pkt.ip_src_address =
+ fr_udp_pkt->ip_pkt.ip_dst_address;
+ fr_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+
+ /* fill in IP checksum */
+ fr_udp_pkt->ip_pkt.hdr_checksum = 0;
+ fr_udp_pkt->ip_pkt.hdr_checksum =
+ calc_checksum(&data[/*sizeof(fr_encap_hdr_t)*/0],
+ sizeof(ip_pkt_t));
+
+ return len;
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+ unsigned short temp;
+ unsigned long sum=0;
+ int i;
+
+ for( i = 0; i <len; i+=2 ) {
+ memcpy(&temp,&data[i],2);
+ sum += (unsigned long)temp;
+ }
+
+ while (sum >> 16 ) {
+ sum = (sum & 0xffffUL) + (sum >> 16);
+ }
+
+ temp = (unsigned short)sum;
+ temp = ~temp;
+
+ if( temp == 0 )
+ temp = 0xffff;
+
+ return temp;
+}
+
+/*
+ If incoming is 0 (outgoing)- if the net numbers is ours make it 0
+ if incoming is 1 - if the net number is 0 make it ours
+
+*/
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, unsigned char incoming)
+{
+ unsigned long pnetwork_number;
+
+ pnetwork_number = (unsigned long)((sendpacket[14] << 24) +
+ (sendpacket[15] << 16) + (sendpacket[16] << 8) +
+ sendpacket[17]);
+
+ if (!incoming) {
+ /* If the destination network number is ours, make it 0 */
+ if( pnetwork_number == network_number) {
+ sendpacket[14] = sendpacket[15] = sendpacket[16] =
+ sendpacket[17] = 0x00;
+ }
+ } else {
+ /* If the incoming network is 0, make it ours */
+ if( pnetwork_number == 0) {
+ sendpacket[14] = (unsigned char)(network_number >> 24);
+ sendpacket[15] = (unsigned char)((network_number &
+ 0x00FF0000) >> 16);
+ sendpacket[16] = (unsigned char)((network_number &
+ 0x0000FF00) >> 8);
+ sendpacket[17] = (unsigned char)(network_number &
+ 0x000000FF);
+ }
+ }
+
+
+ pnetwork_number = (unsigned long)((sendpacket[26] << 24) +
+ (sendpacket[27] << 16) + (sendpacket[28] << 8) +
+ sendpacket[29]);
+
+ if( !incoming ) {
+ /* If the source network is ours, make it 0 */
+ if( pnetwork_number == network_number) {
+ sendpacket[26] = sendpacket[27] = sendpacket[28] =
+ sendpacket[29] = 0x00;
+ }
+ } else {
+ /* If the source network is 0, make it ours */
+ if( pnetwork_number == 0 ) {
+ sendpacket[26] = (unsigned char)(network_number >> 24);
+ sendpacket[27] = (unsigned char)((network_number &
+ 0x00FF0000) >> 16);
+ sendpacket[28] = (unsigned char)((network_number &
+ 0x0000FF00) >> 8);
+ sendpacket[29] = (unsigned char)(network_number &
+ 0x000000FF);
+ }
+ }
+} /* switch_net_numbers */
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ */
+static struct net_device_stats *if_stats(struct net_device *dev)
+{
+ fr_channel_t* chan = dev->priv;
+
+ if(chan == NULL)
+ return NULL;
+
+ return &chan->ifstats;
+}
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * fr_isr: S508 frame relay interrupt service routine.
+ *
+ * Description:
+ * Frame relay main interrupt service route. This
+ * function check the interrupt type and takes
+ * the appropriate action.
+ */
+static void fr_isr (sdla_t* card)
+{
+ fr508_flags_t* flags = card->flags;
+ char *ptr = &flags->iflag;
+ int i,err;
+ fr_mbox_t* mbox = card->mbox;
+
+ /* This flag prevents nesting of interrupts. See sdla_isr() routine
+ * in sdlamain.c. */
+ card->in_isr = 1;
+
+ ++card->statistics.isr_entry;
+
+
+ /* All peripheral (configuraiton, re-configuration) events
+ * take presidence over the ISR. Thus, retrigger */
+ if (test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+ ++card->statistics.isr_already_critical;
+ goto fr_isr_exit;
+ }
+
+ if(card->hw.type != SDLA_S514) {
+ if (test_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+ printk(KERN_INFO "%s: Critical while in ISR: If Send Running!\n",
+ card->devname);
+ ++card->statistics.isr_already_critical;
+ goto fr_isr_exit;
+ }
+ }
+
+ switch (flags->iflag) {
+
+ case FR_INTR_RXRDY: /* receive interrupt */
+ ++card->statistics.isr_rx;
+ rx_intr(card);
+ break;
+
+
+ case FR_INTR_TXRDY: /* transmit interrupt */
+ ++ card->statistics.isr_tx;
+ tx_intr(card);
+ break;
+
+ case FR_INTR_READY:
+ Intr_test_counter++;
+ ++card->statistics.isr_intr_test;
+ break;
+
+ case FR_INTR_DLC: /* Event interrupt occurred */
+ mbox->cmd.command = FR_READ_STATUS;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err)
+ fr_event(card, err, mbox);
+ break;
+
+ case FR_INTR_TIMER: /* Timer interrupt */
+ timer_intr(card);
+ break;
+
+ default:
+ ++card->statistics.isr_spurious;
+ spur_intr(card);
+ printk(KERN_INFO "%s: Interrupt Type 0x%02X!\n",
+ card->devname, flags->iflag);
+
+ printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ for(i = 0; i < 8; i ++)
+ printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+ printk(KERN_INFO "\n");
+
+ break;
+ }
+
+fr_isr_exit:
+
+ card->in_isr = 0;
+ flags->iflag = 0;
+ return;
+}
+
+
+
+/*===========================================================
+ * rx_intr Receive interrupt handler.
+ *
+ * Description
+ * Upon receiveing an interrupt:
+ * 1. Check that the firmware is in sync with
+ * the driver.
+ * 2. Find an appropriate network interface
+ * based on the received dlci number.
+ * 3. Check that the netowrk interface exists
+ * and that it's setup properly.
+ * 4. Copy the data into an skb buffer.
+ * 5. Check the packet type and take
+ * appropriate acton: UPD, API, ARP or Data.
+ */
+
+static void rx_intr (sdla_t* card)
+{
+ fr_rx_buf_ctl_t* frbuf = card->rxmb;
+ fr508_flags_t* flags = card->flags;
+ fr_channel_t* chan;
+ char *ptr = &flags->iflag;
+ struct sk_buff* skb;
+ struct net_device* dev;
+ void* buf;
+ unsigned dlci, len, offs, len_incl_hdr;
+ int i, udp_type;
+
+
+ /* Check that firmware buffers are in sync */
+ if (frbuf->flag != 0x01) {
+
+ printk(KERN_INFO
+ "%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n",
+ card->devname, (unsigned)frbuf, frbuf->flag);
+
+ printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ for(i = 0; i < 8; i ++)
+ printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+ printk(KERN_INFO "\n");
+
+ ++card->statistics.rx_intr_corrupt_rx_bfr;
+
+ /* Bug Fix: Mar 6 2000
+ * If we get a corrupted mailbox, it means that driver
+ * is out of sync with the firmware. There is no recovery.
+ * If we don't turn off all interrupts for this card
+ * the machine will crash.
+ */
+ printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+ printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+ fr_set_intr_mode(card, 0, 0, 0);
+ return;
+ }
+
+ len = frbuf->length;
+ dlci = frbuf->dlci;
+ offs = frbuf->offset;
+
+ /* Find the network interface for this packet */
+ dev = find_channel(card, dlci);
+
+
+ /* Check that the network interface is active and
+ * properly setup */
+ if (dev == NULL) {
+ if( net_ratelimit()) {
+ printk(KERN_INFO "%s: received data on unconfigured DLCI %d!\n",
+ card->devname, dlci);
+ }
+ ++card->statistics.rx_intr_on_orphaned_DLCI;
+ ++card->wandev.stats.rx_dropped;
+ goto rx_done;
+ }
+
+ if ((chan = dev->priv) == NULL){
+ if( net_ratelimit()) {
+ printk(KERN_INFO "%s: received data on unconfigured DLCI %d!\n",
+ card->devname, dlci);
+ }
+ ++card->statistics.rx_intr_on_orphaned_DLCI;
+ ++card->wandev.stats.rx_dropped;
+ goto rx_done;
+ }
+
+ skb = dev_alloc_skb(len);
+
+ if (!netif_running(dev) || (skb == NULL)){
+
+ ++chan->ifstats.rx_dropped;
+
+ if(skb == NULL) {
+ if (net_ratelimit()) {
+ printk(KERN_INFO
+ "%s: no socket buffers available!\n",
+ card->devname);
+ }
+ chan->drvstats_rx_intr.rx_intr_no_socket ++;
+ }
+
+ if (!netif_running(dev)){
+ chan->drvstats_rx_intr.
+ rx_intr_dev_not_started ++;
+ if (skb){
+ dev_kfree_skb_any(skb);
+ }
+ }
+ goto rx_done;
+ }
+
+ /* Copy data from the board into the socket buffer */
+ if ((offs + len) > card->u.f.rx_top + 1) {
+ unsigned tmp = card->u.f.rx_top - offs + 1;
+
+ buf = skb_put(skb, tmp);
+ sdla_peek(&card->hw, offs, buf, tmp);
+ offs = card->u.f.rx_base;
+ len -= tmp;
+ }
+
+ buf = skb_put(skb, len);
+ sdla_peek(&card->hw, offs, buf, len);
+
+
+ /* We got the packet from the bard.
+ * Check the packet type and take appropriate action */
+
+ udp_type = udp_pkt_type( skb, card );
+
+ if(udp_type != UDP_INVALID_TYPE) {
+
+ /* UDP Debug packet received, store the
+ * packet and handle it in timer interrupt */
+
+ skb_pull(skb, 1);
+ if (wanrouter_type_trans(skb, dev)){
+ if(store_udp_mgmt_pkt(udp_type,UDP_PKT_FRM_NETWORK,card,skb,dlci)){
+
+ flags->imask |= FR_INTR_TIMER;
+
+ if (udp_type == UDP_FPIPE_TYPE){
+ ++chan->drvstats_rx_intr.rx_intr_PIPE_request;
+ }
+ }
+ }
+
+ }else if (chan->common.usedby == API) {
+
+ /* We are in API mode.
+ * Add an API header to the RAW packet
+ * and queue it into a circular buffer.
+ * Then kick the fr_bh() bottom half handler */
+
+ api_rx_hdr_t* api_rx_hdr;
+ chan->drvstats_rx_intr.rx_intr_bfr_passed_to_stack ++;
+ chan->ifstats.rx_packets ++;
+ card->wandev.stats.rx_packets ++;
+
+ chan->ifstats.rx_bytes += skb->len;
+ card->wandev.stats.rx_bytes += skb->len;
+
+ skb_push(skb, sizeof(api_rx_hdr_t));
+ api_rx_hdr = (api_rx_hdr_t*)&skb->data[0x00];
+ api_rx_hdr->attr = frbuf->attr;
+ api_rx_hdr->time_stamp = frbuf->tmstamp;
+
+ skb->protocol = htons(ETH_P_IP);
+ skb->mac.raw = skb->data;
+ skb->dev = dev;
+ skb->pkt_type = WAN_PACKET_DATA;
+
+ bh_enqueue(dev, skb);
+
+ trigger_fr_bh(chan);
+
+ }else if (handle_IPXWAN(skb->data,chan->name,chan->enable_IPX, chan->network_number)){
+
+ //FIXME: Frame Relay IPX is not supported, Yet !
+ //if (chan->enable_IPX) {
+ // fr_send(card, dlci, 0, skb->len,skb->data);
+ //}
+ dev_kfree_skb_any(skb);
+
+ } else if (is_arp(skb->data)) {
+
+ /* ARP support enabled Mar 16 2000
+ * Process incoming ARP reply/request, setup
+ * dynamic routes. */
+
+ if (process_ARP((arphdr_1490_t *)skb->data, card, dev)) {
+ if (net_ratelimit()){
+ printk (KERN_INFO
+ "%s: Error processing ARP Packet.\n",
+ card->devname);
+ }
+ }
+ dev_kfree_skb_any(skb);
+
+ } else if (skb->data[0] != 0x03) {
+
+ if (net_ratelimit()) {
+ printk(KERN_INFO "%s: Non IETF packet discarded.\n",
+ card->devname);
+ }
+ dev_kfree_skb_any(skb);
+
+ } else {
+
+ len_incl_hdr = skb->len;
+ /* Decapsulate packet and pass it up the
+ protocol stack */
+ skb->dev = dev;
+
+ if (chan->common.usedby == BRIDGE || chan->common.usedby == BRIDGE_NODE){
+
+ /* Make sure it's an Ethernet frame, otherwise drop it */
+ if (!memcmp(skb->data, "\x03\x00\x80\x00\x80\xC2\x00\x07", 8)) {
+ skb_pull(skb, 8);
+ skb->protocol=eth_type_trans(skb,dev);
+ }else{
+ ++chan->drvstats_rx_intr.rx_intr_bfr_not_passed_to_stack;
+ ++chan->ifstats.rx_errors;
+ ++card->wandev.stats.rx_errors;
+ goto rx_done;
+ }
+ }else{
+
+ /* remove hardware header */
+ buf = skb_pull(skb, 1);
+
+ if (!wanrouter_type_trans(skb, dev)) {
+
+ /* can't decapsulate packet */
+ dev_kfree_skb_any(skb);
+
+ ++chan->drvstats_rx_intr.rx_intr_bfr_not_passed_to_stack;
+ ++chan->ifstats.rx_errors;
+ ++card->wandev.stats.rx_errors;
+ goto rx_done;
+ }
+ skb->mac.raw = skb->data;
+ }
+
+
+ /* Send a packet up the IP stack */
+ skb->dev->last_rx = jiffies;
+ netif_rx(skb);
+ ++chan->drvstats_rx_intr.rx_intr_bfr_passed_to_stack;
+ ++chan->ifstats.rx_packets;
+ ++card->wandev.stats.rx_packets;
+
+ chan->ifstats.rx_bytes += len_incl_hdr;
+ card->wandev.stats.rx_bytes += len_incl_hdr;
+ }
+
+rx_done:
+
+ /* Release buffer element and calculate a pointer to the next one */
+ frbuf->flag = 0;
+ card->rxmb = ++frbuf;
+ if ((void*)frbuf > card->u.f.rxmb_last)
+ card->rxmb = card->u.f.rxmb_base;
+
+}
+
+/*==================================================================
+ * tx_intr: Transmit interrupt handler.
+ *
+ * Rationale:
+ * If the board is busy transmitting, if_send() will
+ * buffers a single packet and turn on
+ * the tx interrupt. Tx interrupt will be called
+ * by the board, once the firmware can send more
+ * data. Thus, no polling is required.
+ *
+ * Description:
+ * Tx interrupt is called for each
+ * configured dlci channel. Thus:
+ * 1. Obtain the netowrk interface based on the
+ * dlci number.
+ * 2. Check that network interface is up and
+ * properly setup.
+ * 3. Check for a buffered packet.
+ * 4. Transmit the packet.
+ * 5. If we are in WANPIPE mode, mark the
+ * NET_BH handler.
+ * 6. If we are in API mode, kick
+ * the AF_WANPIPE socket for more data.
+ *
+ */
+static void tx_intr(sdla_t *card)
+{
+ fr508_flags_t* flags = card->flags;
+ fr_tx_buf_ctl_t* bctl;
+ struct net_device* dev;
+ fr_channel_t* chan;
+
+ if(card->hw.type == SDLA_S514){
+ bctl = (void*)(flags->tse_offs + card->hw.dpmbase);
+ }else{
+ bctl = (void*)(flags->tse_offs - FR_MB_VECTOR +
+ card->hw.dpmbase);
+ }
+
+ /* Find the structure and make it unbusy */
+ dev = find_channel(card, flags->dlci);
+ if (dev == NULL){
+ printk(KERN_INFO "NO DEV IN TX Interrupt\n");
+ goto end_of_tx_intr;
+ }
+
+ if ((chan = dev->priv) == NULL){
+ printk(KERN_INFO "NO CHAN IN TX Interrupt\n");
+ goto end_of_tx_intr;
+ }
+
+ if(!chan->transmit_length || !chan->delay_skb) {
+ printk(KERN_INFO "%s: tx int error - transmit length zero\n",
+ card->wandev.name);
+ goto end_of_tx_intr;
+ }
+
+ /* If the 'if_send()' procedure is currently checking the 'tbusy'
+ status, then we cannot transmit. Instead, we configure the microcode
+ so as to re-issue this transmit interrupt at a later stage.
+ */
+ if (test_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical)) {
+
+ fr_dlci_interface_t* dlci_interface = chan->dlci_int_interface;
+ bctl->flag = 0xA0;
+ dlci_interface->gen_interrupt |= FR_INTR_TXRDY;
+ return;
+
+ }else{
+ bctl->dlci = flags->dlci;
+ bctl->length = chan->transmit_length+chan->fr_header_len;
+ sdla_poke(&card->hw,
+ fr_send_hdr(card,bctl->dlci,bctl->offset),
+ chan->delay_skb->data,
+ chan->delay_skb->len);
+ bctl->flag = 0xC0;
+
+ ++chan->ifstats.tx_packets;
+ ++card->wandev.stats.tx_packets;
+ chan->ifstats.tx_bytes += chan->transmit_length;
+ card->wandev.stats.tx_bytes += chan->transmit_length;
+
+ /* We must free an sk buffer, which we used
+ * for delayed transmission; Otherwise, the sock
+ * will run out of memory */
+ dev_kfree_skb_any(chan->delay_skb);
+
+ chan->delay_skb = NULL;
+ chan->transmit_length = 0;
+
+ dev->trans_start = jiffies;
+
+ if (netif_queue_stopped(dev)){
+ /* If using API, than wakeup socket BH handler */
+ if (chan->common.usedby == API){
+ netif_start_queue(dev);
+ wakeup_sk_bh(dev);
+ }else{
+ netif_wake_queue(dev);
+ }
+ }
+ }
+
+end_of_tx_intr:
+
+ /* if any other interfaces have transmit interrupts pending,
+ * do not disable the global transmit interrupt */
+ if(!(-- card->u.f.tx_interrupts_pending))
+ flags->imask &= ~FR_INTR_TXRDY;
+
+
+}
+
+
+/*============================================================================
+ * timer_intr: Timer interrupt handler.
+ *
+ * Rationale:
+ * All commans must be executed within the timer
+ * interrupt since no two commands should execute
+ * at the same time.
+ *
+ * Description:
+ * The timer interrupt is used to:
+ * 1. Processing udp calls from 'fpipemon'.
+ * 2. Processing update calls from /proc file system
+ * 3. Reading board-level statistics for
+ * updating the proc file system.
+ * 4. Sending inverse ARP request packets.
+ * 5. Configure a dlci/channel.
+ * 6. Unconfigure a dlci/channel. (Node only)
+ */
+
+static void timer_intr(sdla_t *card)
+{
+ fr508_flags_t* flags = card->flags;
+
+ /* UDP Debuging: fpipemon call */
+ if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UDP) {
+ if(card->u.f.udp_type == UDP_FPIPE_TYPE) {
+ if(process_udp_mgmt_pkt(card)) {
+ card->u.f.timer_int_enabled &=
+ ~TMR_INT_ENABLED_UDP;
+ }
+ }
+ }
+
+ /* /proc update call : triggered from update() */
+ if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UPDATE) {
+ fr_get_err_stats(card);
+ fr_get_stats(card);
+ card->u.f.update_comms_stats = 0;
+ card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE;
+ }
+
+ /* Update the channel state call. This is call is
+ * triggered by if_send() function */
+ if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UPDATE_STATE){
+ struct net_device *dev;
+ if (card->wandev.state == WAN_CONNECTED){
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)){
+ fr_channel_t *chan = dev->priv;
+ if (chan->common.state != WAN_CONNECTED){
+ update_chan_state(dev);
+ }
+ }
+ }
+ card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE_STATE;
+ }
+
+ /* configure a dlci/channel */
+ if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_CONFIG){
+ config_fr(card);
+ card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_CONFIG;
+ }
+
+ /* unconfigure a dlci/channel */
+ if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_UNCONFIG){
+ unconfig_fr(card);
+ card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UNCONFIG;
+ }
+
+
+ /* Transmit ARP packets */
+ if (card->u.f.timer_int_enabled & TMR_INT_ENABLED_ARP){
+ int i=0;
+ struct net_device *dev;
+
+ if (card->u.f.arp_dev == NULL)
+ card->u.f.arp_dev = card->wandev.dev;
+
+ dev = card->u.f.arp_dev;
+
+ for (;;){
+
+ fr_channel_t *chan = dev->priv;
+
+ /* If the interface is brought down cancel sending In-ARPs */
+ if (!(dev->flags&IFF_UP)){
+ clear_bit(0,&chan->inarp_ready);
+ }
+
+ if (test_bit(0,&chan->inarp_ready)){
+
+ if (check_tx_status(card,dev)){
+ set_bit(ARP_CRIT,&card->wandev.critical);
+ break;
+ }
+
+ if (!send_inarp_request(card,dev)){
+ trigger_fr_arp(dev);
+ chan->inarp_tick = jiffies;
+ }
+
+ clear_bit(0,&chan->inarp_ready);
+ dev = move_dev_to_next(card,dev);
+ break;
+ }
+ dev = move_dev_to_next(card,dev);
+
+ if (++i == card->wandev.new_if_cnt){
+ card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_ARP;
+ break;
+ }
+ }
+ card->u.f.arp_dev = dev;
+ }
+
+ if(!card->u.f.timer_int_enabled)
+ flags->imask &= ~FR_INTR_TIMER;
+}
+
+
+/*============================================================================
+ * spur_intr: Spurious interrupt handler.
+ *
+ * Description:
+ * We don't know this interrupt.
+ * Print a warning.
+ */
+
+static void spur_intr (sdla_t* card)
+{
+ if (net_ratelimit()){
+ printk(KERN_INFO "%s: spurious interrupt!\n", card->devname);
+ }
+}
+
+
+//FIXME: Fix the IPX in next version
+/*===========================================================================
+ * Return 0 for non-IPXWAN packet
+ * 1 for IPXWAN packet or IPX is not enabled!
+ * FIXME: Use a IPX structure here not offsets
+ */
+static int handle_IPXWAN(unsigned char *sendpacket,
+ char *devname, unsigned char enable_IPX,
+ unsigned long network_number)
+{
+ int i;
+
+ if( sendpacket[1] == 0x00 && sendpacket[2] == 0x80 &&
+ sendpacket[6] == 0x81 && sendpacket[7] == 0x37) {
+
+ /* It's an IPX packet */
+ if (!enable_IPX){
+ /* Return 1 so we don't pass it up the stack. */
+ //FIXME: Take this out when IPX is fixed
+ if (net_ratelimit()){
+ printk (KERN_INFO
+ "%s: WARNING: Unsupported IPX packet received and dropped\n",
+ devname);
+ }
+ return 1;
+ }
+ } else {
+ /* It's not IPX so return and pass it up the stack. */
+ return 0;
+ }
+
+ if( sendpacket[24] == 0x90 && sendpacket[25] == 0x04){
+ /* It's IPXWAN */
+
+ if( sendpacket[10] == 0x02 && sendpacket[42] == 0x00){
+
+ /* It's a timer request packet */
+ printk(KERN_INFO "%s: Received IPXWAN Timer Request packet\n",
+ devname);
+
+ /* Go through the routing options and answer no to every
+ * option except Unnumbered RIP/SAP
+ */
+ for(i = 49; sendpacket[i] == 0x00; i += 5){
+ /* 0x02 is the option for Unnumbered RIP/SAP */
+ if( sendpacket[i + 4] != 0x02){
+ sendpacket[i + 1] = 0;
+ }
+ }
+
+ /* Skip over the extended Node ID option */
+ if( sendpacket[i] == 0x04 ){
+ i += 8;
+ }
+
+ /* We also want to turn off all header compression opt.
+ */
+ for(; sendpacket[i] == 0x80 ;){
+ sendpacket[i + 1] = 0;
+ i += (sendpacket[i + 2] << 8) + (sendpacket[i + 3]) + 4;
+ }
+
+ /* Set the packet type to timer response */
+ sendpacket[42] = 0x01;
+
+ printk(KERN_INFO "%s: Sending IPXWAN Timer Response\n",
+ devname);
+
+ } else if( sendpacket[42] == 0x02 ){
+
+ /* This is an information request packet */
+ printk(KERN_INFO
+ "%s: Received IPXWAN Information Request packet\n",
+ devname);
+
+ /* Set the packet type to information response */
+ sendpacket[42] = 0x03;
+
+ /* Set the router name */
+ sendpacket[59] = 'F';
+ sendpacket[60] = 'P';
+ sendpacket[61] = 'I';
+ sendpacket[62] = 'P';
+ sendpacket[63] = 'E';
+ sendpacket[64] = '-';
+ sendpacket[65] = CVHexToAscii(network_number >> 28);
+ sendpacket[66] = CVHexToAscii((network_number & 0x0F000000)>> 24);
+ sendpacket[67] = CVHexToAscii((network_number & 0x00F00000)>> 20);
+ sendpacket[68] = CVHexToAscii((network_number & 0x000F0000)>> 16);
+ sendpacket[69] = CVHexToAscii((network_number & 0x0000F000)>> 12);
+ sendpacket[70] = CVHexToAscii((network_number & 0x00000F00)>> 8);
+ sendpacket[71] = CVHexToAscii((network_number & 0x000000F0)>> 4);
+ sendpacket[72] = CVHexToAscii(network_number & 0x0000000F);
+ for(i = 73; i < 107; i+= 1)
+ {
+ sendpacket[i] = 0;
+ }
+
+ printk(KERN_INFO "%s: Sending IPXWAN Information Response packet\n",
+ devname);
+ } else {
+
+ printk(KERN_INFO "%s: Unknown IPXWAN packet!\n",devname);
+ return 0;
+ }
+
+ /* Set the WNodeID to our network address */
+ sendpacket[43] = (unsigned char)(network_number >> 24);
+ sendpacket[44] = (unsigned char)((network_number & 0x00FF0000) >> 16);
+ sendpacket[45] = (unsigned char)((network_number & 0x0000FF00) >> 8);
+ sendpacket[46] = (unsigned char)(network_number & 0x000000FF);
+
+ return 1;
+ }
+
+ /* If we get here, it's an IPX-data packet so it'll get passed up the
+ * stack.
+ * switch the network numbers
+ */
+ switch_net_numbers(sendpacket, network_number ,1);
+ return 0;
+}
+/*============================================================================
+ * process_route
+ *
+ * Rationale:
+ * If the interface goes down, or we receive an ARP request,
+ * we have to change the network interface ip addresses.
+ * This cannot be done within the interrupt.
+ *
+ * Description:
+ *
+ * This routine is called as a polling routine to dynamically
+ * add/delete routes negotiated by inverse ARP. It is in this
+ * "task" because we don't want routes to be added while in
+ * interrupt context.
+ *
+ * Usage:
+ * This function is called by fr_poll() polling funtion.
+ */
+
+static void process_route(struct net_device *dev)
+{
+ fr_channel_t *chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ struct ifreq if_info;
+ struct sockaddr_in *if_data;
+ mm_segment_t fs = get_fs();
+ u32 ip_tmp;
+ int err;
+
+
+ switch(chan->route_flag){
+
+ case ADD_ROUTE:
+
+ /* Set remote addresses */
+ memset(&if_info, 0, sizeof(if_info));
+ strcpy(if_info.ifr_name, dev->name);
+
+ set_fs(get_ds()); /* get user space block */
+
+ if_data = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+ if_data->sin_addr.s_addr = chan->ip_remote;
+ if_data->sin_family = AF_INET;
+ err = devinet_ioctl( SIOCSIFDSTADDR, &if_info );
+
+ set_fs(fs); /* restore old block */
+
+ if (err) {
+ printk(KERN_INFO
+ "%s: Route Add failed. Error: %d\n",
+ card->devname,err);
+ printk(KERN_INFO "%s: Address: %u.%u.%u.%u\n",
+ chan->name, NIPQUAD(chan->ip_remote));
+
+ }else {
+ printk(KERN_INFO "%s: Route Added Successfully: %u.%u.%u.%u\n",
+ card->devname,NIPQUAD(chan->ip_remote));
+ chan->route_flag = ROUTE_ADDED;
+ }
+ break;
+
+ case REMOVE_ROUTE:
+
+ /* Set remote addresses */
+ memset(&if_info, 0, sizeof(if_info));
+ strcpy(if_info.ifr_name, dev->name);
+
+ ip_tmp = get_ip_address(dev,WAN_POINTOPOINT_IP);
+
+ set_fs(get_ds()); /* get user space block */
+
+ if_data = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+ if_data->sin_addr.s_addr = 0;
+ if_data->sin_family = AF_INET;
+ err = devinet_ioctl( SIOCSIFDSTADDR, &if_info );
+
+ set_fs(fs);
+
+ if (err) {
+ printk(KERN_INFO
+ "%s: Deleting of route failed. Error: %d\n",
+ card->devname,err);
+ printk(KERN_INFO "%s: Address: %u.%u.%u.%u\n",
+ dev->name,NIPQUAD(chan->ip_remote) );
+
+ } else {
+ printk(KERN_INFO "%s: Route Removed Sucessfuly: %u.%u.%u.%u\n",
+ card->devname,NIPQUAD(ip_tmp));
+ chan->route_flag = NO_ROUTE;
+ }
+ break;
+
+ } /* Case Statement */
+
+}
+
+
+
+/****** Frame Relay Firmware-Specific Functions *****************************/
+
+/*============================================================================
+ * Read firmware code version.
+ * o fill string str with firmware version info.
+ */
+static int fr_read_version (sdla_t* card, char* str)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ mbox->cmd.command = FR_READ_CODE_VERSION;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if (!err && str) {
+ int len = mbox->cmd.length;
+ memcpy(str, mbox->data, len);
+ str[len] = '\0';
+ }
+ return err;
+}
+
+/*============================================================================
+ * Set global configuration.
+ */
+static int fr_configure (sdla_t* card, fr_conf_t *conf)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int dlci_num = card->u.f.dlci_num;
+ int err, i;
+
+ do
+ {
+ memcpy(mbox->data, conf, sizeof(fr_conf_t));
+
+ if (dlci_num) for (i = 0; i < dlci_num; ++i)
+ ((fr_conf_t*)mbox->data)->dlci[i] =
+ card->u.f.node_dlci[i];
+
+ mbox->cmd.command = FR_SET_CONFIG;
+ mbox->cmd.length =
+ sizeof(fr_conf_t) + dlci_num * sizeof(short);
+
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ /*NC Oct 12 2000 */
+ if (err != CMD_OK){
+ printk(KERN_ERR "%s: Frame Relay Configuration Failed: rc=0x%x\n",
+ card->devname,err);
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Set DLCI configuration.
+ */
+static int fr_dlci_configure (sdla_t* card, fr_dlc_conf_t *conf, unsigned dlci)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memcpy(mbox->data, conf, sizeof(fr_dlc_conf_t));
+ mbox->cmd.dlci = (unsigned short) dlci;
+ mbox->cmd.command = FR_SET_CONFIG;
+ mbox->cmd.length = sizeof(fr_dlc_conf_t);
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry--);
+
+ return err;
+}
+/*============================================================================
+ * Set interrupt mode.
+ */
+static int fr_set_intr_mode (sdla_t* card, unsigned mode, unsigned mtu,
+ unsigned short timeout)
+{
+ fr_mbox_t* mbox = card->mbox;
+ fr508_intr_ctl_t* ictl = (void*)mbox->data;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(ictl, 0, sizeof(fr508_intr_ctl_t));
+ ictl->mode = mode;
+ ictl->tx_len = mtu;
+ ictl->irq = card->hw.irq;
+
+ /* indicate timeout on timer */
+ if (mode & 0x20) ictl->timeout = timeout;
+
+ mbox->cmd.length = sizeof(fr508_intr_ctl_t);
+ mbox->cmd.command = FR_SET_INTR_MODE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ return err;
+}
+
+/*============================================================================
+ * Enable communications.
+ */
+static int fr_comm_enable (sdla_t* card)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ mbox->cmd.command = FR_COMM_ENABLE;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ return err;
+}
+
+/*============================================================================
+ * fr_comm_disable
+ *
+ * Warning: This functin is called by the shutdown() procedure. It is void
+ * since dev->priv are has already been deallocated and no
+ * error checking is possible using fr_event() function.
+ */
+static void fr_comm_disable (sdla_t* card)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do {
+ mbox->cmd.command = FR_SET_MODEM_STATUS;
+ mbox->cmd.length = 1;
+ mbox->data[0] = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry--);
+
+ retry = MAX_CMD_RETRY;
+
+ do
+ {
+ mbox->cmd.command = FR_COMM_DISABLE;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry--);
+
+ return;
+}
+
+
+
+/*============================================================================
+ * Get communications error statistics.
+ */
+static int fr_get_err_stats (sdla_t* card)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+
+ do
+ {
+ mbox->cmd.command = FR_READ_ERROR_STATS;
+ mbox->cmd.length = 0;
+ mbox->cmd.dlci = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if (!err) {
+ fr_comm_stat_t* stats = (void*)mbox->data;
+ card->wandev.stats.rx_over_errors = stats->rx_overruns;
+ card->wandev.stats.rx_crc_errors = stats->rx_bad_crc;
+ card->wandev.stats.rx_missed_errors = stats->rx_aborts;
+ card->wandev.stats.rx_length_errors = stats->rx_too_long;
+ card->wandev.stats.tx_aborted_errors = stats->tx_aborts;
+
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Get statistics.
+ */
+static int fr_get_stats (sdla_t* card)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+
+ do
+ {
+ mbox->cmd.command = FR_READ_STATISTICS;
+ mbox->cmd.length = 0;
+ mbox->cmd.dlci = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if (!err) {
+ fr_link_stat_t* stats = (void*)mbox->data;
+ card->wandev.stats.rx_frame_errors = stats->rx_bad_format;
+ card->wandev.stats.rx_dropped =
+ stats->rx_dropped + stats->rx_dropped2;
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Add DLCI(s) (Access Node only!).
+ * This routine will perform the ADD_DLCIs command for the specified DLCI.
+ */
+static int fr_add_dlci (sdla_t* card, int dlci)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ unsigned short* dlci_list = (void*)mbox->data;
+
+ mbox->cmd.length = sizeof(short);
+ dlci_list[0] = dlci;
+ mbox->cmd.command = FR_ADD_DLCI;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ return err;
+}
+
+/*============================================================================
+ * Activate DLCI(s) (Access Node only!).
+ * This routine will perform the ACTIVATE_DLCIs command with a DLCI number.
+ */
+static int fr_activate_dlci (sdla_t* card, int dlci)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ unsigned short* dlci_list = (void*)mbox->data;
+
+ mbox->cmd.length = sizeof(short);
+ dlci_list[0] = dlci;
+ mbox->cmd.command = FR_ACTIVATE_DLCI;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ return err;
+}
+
+/*============================================================================
+ * Delete DLCI(s) (Access Node only!).
+ * This routine will perform the DELETE_DLCIs command with a DLCI number.
+ */
+static int fr_delete_dlci (sdla_t* card, int dlci)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ unsigned short* dlci_list = (void*)mbox->data;
+
+ mbox->cmd.length = sizeof(short);
+ dlci_list[0] = dlci;
+ mbox->cmd.command = FR_DELETE_DLCI;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ return err;
+}
+
+
+
+/*============================================================================
+ * Issue in-channel signalling frame.
+ */
+static int fr_issue_isf (sdla_t* card, int isf)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ mbox->data[0] = isf;
+ mbox->cmd.length = 1;
+ mbox->cmd.command = FR_ISSUE_IS_FRAME;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ return err;
+}
+
+
+static unsigned int fr_send_hdr (sdla_t*card, int dlci, unsigned int offset)
+{
+ struct net_device *dev = find_channel(card,dlci);
+ fr_channel_t *chan;
+
+ if (!dev || !(chan=dev->priv))
+ return offset;
+
+ if (chan->fr_header_len){
+ sdla_poke(&card->hw, offset, chan->fr_header, chan->fr_header_len);
+ }
+
+ return offset+chan->fr_header_len;
+}
+
+/*============================================================================
+ * Send a frame on a selected DLCI.
+ */
+static int fr_send_data_header (sdla_t* card, int dlci, unsigned char attr, int len,
+ void *buf, unsigned char hdr_len)
+{
+ fr_mbox_t* mbox = card->mbox + 0x800;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ mbox->cmd.dlci = dlci;
+ mbox->cmd.attr = attr;
+ mbox->cmd.length = len+hdr_len;
+ mbox->cmd.command = FR_WRITE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if (!err) {
+ fr_tx_buf_ctl_t* frbuf;
+
+ if(card->hw.type == SDLA_S514)
+ frbuf = (void*)(*(unsigned long*)mbox->data +
+ card->hw.dpmbase);
+ else
+ frbuf = (void*)(*(unsigned long*)mbox->data -
+ FR_MB_VECTOR + card->hw.dpmbase);
+
+ sdla_poke(&card->hw, fr_send_hdr(card,dlci,frbuf->offset), buf, len);
+ frbuf->flag = 0x01;
+ }
+
+ return err;
+}
+
+static int fr_send (sdla_t* card, int dlci, unsigned char attr, int len,
+ void *buf)
+{
+ fr_mbox_t* mbox = card->mbox + 0x800;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ mbox->cmd.dlci = dlci;
+ mbox->cmd.attr = attr;
+ mbox->cmd.length = len;
+ mbox->cmd.command = FR_WRITE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if (!err) {
+ fr_tx_buf_ctl_t* frbuf;
+
+ if(card->hw.type == SDLA_S514)
+ frbuf = (void*)(*(unsigned long*)mbox->data +
+ card->hw.dpmbase);
+ else
+ frbuf = (void*)(*(unsigned long*)mbox->data -
+ FR_MB_VECTOR + card->hw.dpmbase);
+
+ sdla_poke(&card->hw, frbuf->offset, buf, len);
+ frbuf->flag = 0x01;
+ }
+
+ return err;
+}
+
+
+/****** Firmware Asynchronous Event Handlers ********************************/
+
+/*============================================================================
+ * Main asyncronous event/error handler.
+ * This routine is called whenever firmware command returns non-zero
+ * return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int fr_event (sdla_t *card, int event, fr_mbox_t* mbox)
+{
+ fr508_flags_t* flags = card->flags;
+ char *ptr = &flags->iflag;
+ int i;
+
+ switch (event) {
+
+ case FRRES_MODEM_FAILURE:
+ return fr_modem_failure(card, mbox);
+
+ case FRRES_CHANNEL_DOWN: {
+ struct net_device *dev;
+
+ /* Remove all routes from associated DLCI's */
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)) {
+ fr_channel_t *chan = dev->priv;
+ if (chan->route_flag == ROUTE_ADDED) {
+ chan->route_flag = REMOVE_ROUTE;
+ }
+
+ if (chan->inarp == INARP_CONFIGURED) {
+ chan->inarp = INARP_REQUEST;
+ }
+
+ /* If the link becomes disconnected then,
+ * all channels will be disconnected
+ * as well.
+ */
+ set_chan_state(dev,WAN_DISCONNECTED);
+ }
+
+ wanpipe_set_state(card, WAN_DISCONNECTED);
+ return 1;
+ }
+
+ case FRRES_CHANNEL_UP: {
+ struct net_device *dev;
+
+ /* FIXME: Only startup devices that are on the list */
+
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)) {
+
+ set_chan_state(dev,WAN_CONNECTED);
+ }
+
+ wanpipe_set_state(card, WAN_CONNECTED);
+ return 1;
+ }
+
+ case FRRES_DLCI_CHANGE:
+ return fr_dlci_change(card, mbox);
+
+ case FRRES_DLCI_MISMATCH:
+ printk(KERN_INFO "%s: DLCI list mismatch!\n",
+ card->devname);
+ return 1;
+
+ case CMD_TIMEOUT:
+ printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+ card->devname, mbox->cmd.command);
+ printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ for(i = 0; i < 8; i ++)
+ printk(KERN_INFO "0x%02X ", *(ptr + 0x18 + i));
+ printk(KERN_INFO "\n");
+
+ break;
+
+ case FRRES_DLCI_INACTIVE:
+ break;
+
+ case FRRES_CIR_OVERFLOW:
+ break;
+
+ case FRRES_BUFFER_OVERFLOW:
+ break;
+
+ default:
+ printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n"
+ , card->devname, mbox->cmd.command, event);
+ }
+
+ return 0;
+}
+
+/*============================================================================
+ * Handle modem error.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int fr_modem_failure (sdla_t *card, fr_mbox_t* mbox)
+{
+ printk(KERN_INFO "%s: physical link down! (modem error 0x%02X)\n",
+ card->devname, mbox->data[0]);
+
+ switch (mbox->cmd.command){
+ case FR_WRITE:
+
+ case FR_READ:
+ return 0;
+ }
+
+ return 1;
+}
+
+/*============================================================================
+ * Handle DLCI status change.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int fr_dlci_change (sdla_t *card, fr_mbox_t* mbox)
+{
+ dlci_status_t* status = (void*)mbox->data;
+ int cnt = mbox->cmd.length / sizeof(dlci_status_t);
+ fr_channel_t *chan;
+ struct net_device* dev2;
+
+
+ for (; cnt; --cnt, ++status) {
+
+ unsigned short dlci= status->dlci;
+ struct net_device* dev = find_channel(card, dlci);
+
+ if (dev == NULL){
+ printk(KERN_INFO
+ "%s: CPE contains unconfigured DLCI= %d\n",
+ card->devname, dlci);
+
+ printk(KERN_INFO
+ "%s: unconfigured DLCI %d reported by network\n"
+ , card->devname, dlci);
+
+ }else{
+ if (status->state == FR_LINK_INOPER) {
+ printk(KERN_INFO
+ "%s: DLCI %u is inactive!\n",
+ card->devname, dlci);
+
+ if (dev && netif_running(dev))
+ set_chan_state(dev, WAN_DISCONNECTED);
+ }
+
+ if (status->state & FR_DLCI_DELETED) {
+
+ printk(KERN_INFO
+ "%s: DLCI %u has been deleted!\n",
+ card->devname, dlci);
+
+ if (dev && netif_running(dev)){
+
+ fr_channel_t *chan = dev->priv;
+
+ if (chan->route_flag == ROUTE_ADDED) {
+ chan->route_flag = REMOVE_ROUTE;
+ /* The state change will trigger
+ * the fr polling routine */
+ }
+
+ if (chan->inarp == INARP_CONFIGURED) {
+ chan->inarp = INARP_REQUEST;
+ }
+
+ set_chan_state(dev, WAN_DISCONNECTED);
+ }
+
+ } else if (status->state & FR_DLCI_ACTIVE) {
+
+ chan = dev->priv;
+
+ /* This flag is used for configuring specific
+ DLCI(s) when they become active.
+ */
+ chan->dlci_configured = DLCI_CONFIG_PENDING;
+
+ set_chan_state(dev, WAN_CONNECTED);
+
+ }
+ }
+ }
+
+ for (dev2 = card->wandev.dev; dev2;
+ dev2 = *((struct net_device **)dev2->priv)){
+
+ chan = dev2->priv;
+
+ if (chan->dlci_configured == DLCI_CONFIG_PENDING) {
+ if (fr_init_dlci(card, chan)){
+ return 1;
+ }
+ }
+
+ }
+ return 1;
+}
+
+
+static int fr_init_dlci (sdla_t *card, fr_channel_t *chan)
+{
+ fr_dlc_conf_t cfg;
+
+ memset(&cfg, 0, sizeof(cfg));
+
+ if ( chan->cir_status == CIR_DISABLED) {
+
+ cfg.cir_fwd = cfg.cir_bwd = 16;
+ cfg.bc_fwd = cfg.bc_bwd = 16;
+ cfg.conf_flags = 0x0001;
+
+ }else if (chan->cir_status == CIR_ENABLED) {
+
+ cfg.cir_fwd = cfg.cir_bwd = chan->cir;
+ cfg.bc_fwd = cfg.bc_bwd = chan->bc;
+ cfg.be_fwd = cfg.be_bwd = chan->be;
+ cfg.conf_flags = 0x0000;
+ }
+
+ if (fr_dlci_configure( card, &cfg , chan->dlci)){
+ printk(KERN_INFO
+ "%s: DLCI Configure failed for %d\n",
+ card->devname, chan->dlci);
+ return 1;
+ }
+
+ chan->dlci_configured = DLCI_CONFIGURED;
+
+ /* Read the interface byte mapping into the channel
+ * structure.
+ */
+ read_DLCI_IB_mapping( card, chan );
+
+ return 0;
+}
+/******* Miscellaneous ******************************************************/
+
+/*============================================================================
+ * Update channel state.
+ */
+static int update_chan_state(struct net_device* dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ mbox->cmd.command = FR_LIST_ACTIVE_DLCI;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if (!err) {
+
+ unsigned short* list = (void*)mbox->data;
+ int cnt = mbox->cmd.length / sizeof(short);
+
+ err=1;
+
+ for (; cnt; --cnt, ++list) {
+
+ if (*list == chan->dlci) {
+ set_chan_state(dev, WAN_CONNECTED);
+
+
+ /* May 23 2000. NC
+ * When a dlci is added or restarted,
+ * the dlci_int_interface pointer must
+ * be reinitialized. */
+ if (!chan->dlci_int_interface){
+ err=fr_init_dlci (card,chan);
+ }
+ break;
+ }
+ }
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Set channel state.
+ */
+static void set_chan_state(struct net_device* dev, int state)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+
+ if (chan->common.state != state) {
+
+ switch (state) {
+
+ case WAN_CONNECTED:
+ printk(KERN_INFO
+ "%s: Interface %s: DLCI %d connected\n",
+ card->devname, dev->name, chan->dlci);
+
+ /* If the interface was previoulsy down,
+ * bring it up, since the channel is active */
+
+ trigger_fr_poll (dev);
+ trigger_fr_arp (dev);
+ break;
+
+ case WAN_CONNECTING:
+ printk(KERN_INFO
+ "%s: Interface %s: DLCI %d connecting\n",
+ card->devname, dev->name, chan->dlci);
+ break;
+
+ case WAN_DISCONNECTED:
+ printk (KERN_INFO
+ "%s: Interface %s: DLCI %d disconnected!\n",
+ card->devname, dev->name, chan->dlci);
+
+ /* If the interface is up, bring it down,
+ * since the channel is now disconnected */
+ trigger_fr_poll (dev);
+ break;
+ }
+
+ chan->common.state = state;
+ }
+
+ chan->state_tick = jiffies;
+}
+
+/*============================================================================
+ * Find network device by its channel number.
+ *
+ * We need this critical flag because we change
+ * the dlci_to_dev_map outside the interrupt.
+ *
+ * NOTE: del_if() functions updates this array, it uses
+ * the spin locks to avoid corruption.
+ */
+static struct net_device* find_channel(sdla_t* card, unsigned dlci)
+{
+ if(dlci > HIGHEST_VALID_DLCI)
+ return NULL;
+
+ return(card->u.f.dlci_to_dev_map[dlci]);
+}
+
+/*============================================================================
+ * Check to see if a frame can be sent. If no transmit buffers available,
+ * enable transmit interrupts.
+ *
+ * Return: 1 - Tx buffer(s) available
+ * 0 - no buffers available
+ */
+static int is_tx_ready (sdla_t* card, fr_channel_t* chan)
+{
+ unsigned char sb;
+
+ if(card->hw.type == SDLA_S514)
+ return 1;
+
+ sb = inb(card->hw.port);
+ if (sb & 0x02)
+ return 1;
+
+ return 0;
+}
+
+/*============================================================================
+ * Convert decimal string to unsigned integer.
+ * If len != 0 then only 'len' characters of the string are converted.
+ */
+static unsigned int dec_to_uint (unsigned char* str, int len)
+{
+ unsigned val;
+
+ if (!len)
+ len = strlen(str);
+
+ for (val = 0; len && is_digit(*str); ++str, --len)
+ val = (val * 10) + (*str - (unsigned)'0');
+
+ return val;
+}
+
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, int dlci)
+{
+ int udp_pkt_stored = 0;
+
+ struct net_device *dev = find_channel(card, dlci);
+ fr_channel_t *chan;
+
+ if (!dev || !(chan=dev->priv))
+ return 1;
+
+ if(!card->u.f.udp_pkt_lgth && (skb->len <= MAX_LGTH_UDP_MGNT_PKT)){
+ card->u.f.udp_pkt_lgth = skb->len + chan->fr_header_len;
+ card->u.f.udp_type = udp_type;
+ card->u.f.udp_pkt_src = udp_pkt_src;
+ card->u.f.udp_dlci = dlci;
+ memcpy(card->u.f.udp_pkt_data, skb->data, skb->len);
+ card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UDP;
+ udp_pkt_stored = 1;
+
+ }else{
+ printk(KERN_INFO "ERROR: UDP packet not stored for DLCI %d\n",
+ dlci);
+ }
+
+ if(udp_pkt_src == UDP_PKT_FRM_STACK){
+ dev_kfree_skb_any(skb);
+ }else{
+ dev_kfree_skb_any(skb);
+ }
+
+ return(udp_pkt_stored);
+}
+
+
+/*==============================================================================
+ * Process UDP call of type FPIPE8ND
+ */
+static int process_udp_mgmt_pkt(sdla_t* card)
+{
+
+ int c_retry = MAX_CMD_RETRY;
+ unsigned char *buf;
+ unsigned char frames;
+ unsigned int len;
+ unsigned short buffer_length;
+ struct sk_buff *new_skb;
+ fr_mbox_t* mbox = card->mbox;
+ int err;
+ struct timeval tv;
+ int udp_mgmt_req_valid = 1;
+ struct net_device* dev;
+ fr_channel_t* chan;
+ fr_udp_pkt_t *fr_udp_pkt;
+ unsigned short num_trc_els;
+ fr_trc_el_t* ptr_trc_el;
+ fr_trc_el_t trc_el;
+ fpipemon_trc_t* fpipemon_trc;
+
+ char udp_pkt_src = card->u.f.udp_pkt_src;
+ int dlci = card->u.f.udp_dlci;
+
+ /* Find network interface for this packet */
+ dev = find_channel(card, dlci);
+ if (!dev){
+ card->u.f.udp_pkt_lgth = 0;
+ return 1;
+ }
+ if ((chan = dev->priv) == NULL){
+ card->u.f.udp_pkt_lgth = 0;
+ return 1;
+ }
+
+ /* If the UDP packet is from the network, we are going to have to
+ transmit a response. Before doing so, we must check to see that
+ we are not currently transmitting a frame (in 'if_send()') and
+ that we are not already in a 'delayed transmit' state.
+ */
+ if(udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+ if (check_tx_status(card,dev)){
+ card->u.f.udp_pkt_lgth = 0;
+ return 1;
+ }
+ }
+
+ fr_udp_pkt = (fr_udp_pkt_t *)card->u.f.udp_pkt_data;
+
+ if(udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+ switch(fr_udp_pkt->cblock.command) {
+
+ case FR_READ_MODEM_STATUS:
+ case FR_READ_STATUS:
+ case FPIPE_ROUTER_UP_TIME:
+ case FR_READ_ERROR_STATS:
+ case FPIPE_DRIVER_STAT_GEN:
+ case FR_READ_STATISTICS:
+ case FR_READ_ADD_DLC_STATS:
+ case FR_READ_CONFIG:
+ case FR_READ_CODE_VERSION:
+ udp_mgmt_req_valid = 1;
+ break;
+ default:
+ udp_mgmt_req_valid = 0;
+ break;
+ }
+ }
+
+ if(!udp_mgmt_req_valid) {
+ /* set length to 0 */
+ fr_udp_pkt->cblock.length = 0;
+ /* set return code */
+ fr_udp_pkt->cblock.result = 0xCD;
+
+ chan->drvstats_gen.UDP_PIPE_mgmt_direction_err ++;
+
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Warning, Illegal UDP command attempted from network: %x\n",
+ card->devname,fr_udp_pkt->cblock.command);
+ }
+
+ } else {
+
+ switch(fr_udp_pkt->cblock.command) {
+
+ case FPIPE_ENABLE_TRACING:
+ if(!card->TracingEnabled) {
+ do {
+ mbox->cmd.command = FR_SET_TRACE_CONFIG;
+ mbox->cmd.length = 1;
+ mbox->cmd.dlci = 0x00;
+ mbox->data[0] = fr_udp_pkt->data[0] |
+ RESET_TRC;
+ err = sdla_exec(mbox) ?
+ mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && c_retry-- && fr_event(card, err,
+ mbox));
+
+ if(err) {
+ card->TracingEnabled = 0;
+ /* set the return code */
+ fr_udp_pkt->cblock.result =
+ mbox->cmd.result;
+ mbox->cmd.length = 0;
+ break;
+ }
+
+ sdla_peek(&card->hw, NO_TRC_ELEMENTS_OFF,
+ &num_trc_els, 2);
+ sdla_peek(&card->hw, BASE_TRC_ELEMENTS_OFF,
+ &card->u.f.trc_el_base, 4);
+ card->u.f.curr_trc_el = card->u.f.trc_el_base;
+ card->u.f.trc_el_last = card->u.f.curr_trc_el +
+ ((num_trc_els - 1) *
+ sizeof(fr_trc_el_t));
+
+ /* Calculate the maximum trace data area in */
+ /* the UDP packet */
+ card->u.f.trc_bfr_space=(MAX_LGTH_UDP_MGNT_PKT -
+ //sizeof(fr_encap_hdr_t) -
+ sizeof(ip_pkt_t) -
+ sizeof(udp_pkt_t) -
+ sizeof(wp_mgmt_t) -
+ sizeof(cblock_t));
+
+ /* set return code */
+ fr_udp_pkt->cblock.result = 0;
+
+ } else {
+ /* set return code to line trace already
+ enabled */
+ fr_udp_pkt->cblock.result = 1;
+ }
+
+ mbox->cmd.length = 0;
+ card->TracingEnabled = 1;
+ break;
+
+
+ case FPIPE_DISABLE_TRACING:
+ if(card->TracingEnabled) {
+
+ do {
+ mbox->cmd.command = FR_SET_TRACE_CONFIG;
+ mbox->cmd.length = 1;
+ mbox->cmd.dlci = 0x00;
+ mbox->data[0] = ~ACTIVATE_TRC;
+ err = sdla_exec(mbox) ?
+ mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && c_retry-- && fr_event(card, err, mbox));
+ }
+
+ /* set return code */
+ fr_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 0;
+ card->TracingEnabled = 0;
+ break;
+
+ case FPIPE_GET_TRACE_INFO:
+
+ /* Line trace cannot be performed on the 502 */
+ if(!card->TracingEnabled) {
+ /* set return code */
+ fr_udp_pkt->cblock.result = 1;
+ mbox->cmd.length = 0;
+ break;
+ }
+
+ ptr_trc_el = (void *)card->u.f.curr_trc_el;
+
+ buffer_length = 0;
+ fr_udp_pkt->data[0x00] = 0x00;
+
+ for(frames = 0; frames < MAX_FRMS_TRACED; frames ++) {
+
+ sdla_peek(&card->hw, (unsigned long)ptr_trc_el,
+ (void *)&trc_el.flag,
+ sizeof(fr_trc_el_t));
+ if(trc_el.flag == 0x00) {
+ break;
+ }
+ if((card->u.f.trc_bfr_space - buffer_length)
+ < sizeof(fpipemon_trc_hdr_t)) {
+ fr_udp_pkt->data[0x00] |= MORE_TRC_DATA;
+ break;
+ }
+
+ fpipemon_trc =
+ (fpipemon_trc_t *)&fr_udp_pkt->data[buffer_length];
+ fpipemon_trc->fpipemon_trc_hdr.status =
+ trc_el.attr;
+ fpipemon_trc->fpipemon_trc_hdr.tmstamp =
+ trc_el.tmstamp;
+ fpipemon_trc->fpipemon_trc_hdr.length =
+ trc_el.length;
+
+ if(!trc_el.offset || !trc_el.length) {
+
+ fpipemon_trc->fpipemon_trc_hdr.data_passed = 0x00;
+
+ }else if((trc_el.length + sizeof(fpipemon_trc_hdr_t) + 1) >
+ (card->u.f.trc_bfr_space - buffer_length)){
+
+ fpipemon_trc->fpipemon_trc_hdr.data_passed = 0x00;
+ fr_udp_pkt->data[0x00] |= MORE_TRC_DATA;
+
+ }else {
+ fpipemon_trc->fpipemon_trc_hdr.data_passed = 0x01;
+ sdla_peek(&card->hw, trc_el.offset,
+ fpipemon_trc->data,
+ trc_el.length);
+ }
+
+ trc_el.flag = 0x00;
+ sdla_poke(&card->hw, (unsigned long)ptr_trc_el,
+ &trc_el.flag, 1);
+
+ ptr_trc_el ++;
+ if((void *)ptr_trc_el > card->u.f.trc_el_last)
+ ptr_trc_el = (void*)card->u.f.trc_el_base;
+
+ buffer_length += sizeof(fpipemon_trc_hdr_t);
+ if(fpipemon_trc->fpipemon_trc_hdr.data_passed) {
+ buffer_length += trc_el.length;
+ }
+
+ if(fr_udp_pkt->data[0x00] & MORE_TRC_DATA) {
+ break;
+ }
+ }
+
+ if(frames == MAX_FRMS_TRACED) {
+ fr_udp_pkt->data[0x00] |= MORE_TRC_DATA;
+ }
+
+ card->u.f.curr_trc_el = (void *)ptr_trc_el;
+
+ /* set the total number of frames passed */
+ fr_udp_pkt->data[0x00] |=
+ ((frames << 1) & (MAX_FRMS_TRACED << 1));
+
+ /* set the data length and return code */
+ fr_udp_pkt->cblock.length = mbox->cmd.length = buffer_length;
+ fr_udp_pkt->cblock.result = 0;
+ break;
+
+ case FPIPE_FT1_READ_STATUS:
+ sdla_peek(&card->hw, 0xF020,
+ &fr_udp_pkt->data[0x00] , 2);
+ fr_udp_pkt->cblock.length = mbox->cmd.length = 2;
+ fr_udp_pkt->cblock.result = 0;
+ break;
+
+ case FPIPE_FLUSH_DRIVER_STATS:
+ init_chan_statistics(chan);
+ init_global_statistics(card);
+ mbox->cmd.length = 0;
+ break;
+
+ case FPIPE_ROUTER_UP_TIME:
+ do_gettimeofday(&tv);
+ chan->router_up_time = tv.tv_sec -
+ chan->router_start_time;
+ *(unsigned long *)&fr_udp_pkt->data =
+ chan->router_up_time;
+ mbox->cmd.length = fr_udp_pkt->cblock.length = 4;
+ fr_udp_pkt->cblock.result = 0;
+ break;
+
+ case FPIPE_DRIVER_STAT_IFSEND:
+ memcpy(fr_udp_pkt->data,
+ &chan->drvstats_if_send.if_send_entry,
+ sizeof(if_send_stat_t));
+ mbox->cmd.length = fr_udp_pkt->cblock.length =sizeof(if_send_stat_t);
+ fr_udp_pkt->cblock.result = 0;
+ break;
+
+ case FPIPE_DRIVER_STAT_INTR:
+
+ memcpy(fr_udp_pkt->data,
+ &card->statistics.isr_entry,
+ sizeof(global_stats_t));
+
+ memcpy(&fr_udp_pkt->data[sizeof(global_stats_t)],
+ &chan->drvstats_rx_intr.rx_intr_no_socket,
+ sizeof(rx_intr_stat_t));
+
+ mbox->cmd.length = fr_udp_pkt->cblock.length =
+ sizeof(global_stats_t) +
+ sizeof(rx_intr_stat_t);
+ fr_udp_pkt->cblock.result = 0;
+ break;
+
+ case FPIPE_DRIVER_STAT_GEN:
+ memcpy(fr_udp_pkt->data,
+ &chan->drvstats_gen.UDP_PIPE_mgmt_kmalloc_err,
+ sizeof(pipe_mgmt_stat_t));
+
+ memcpy(&fr_udp_pkt->data[sizeof(pipe_mgmt_stat_t)],
+ &card->statistics, sizeof(global_stats_t));
+
+ mbox->cmd.length = fr_udp_pkt->cblock.length = sizeof(global_stats_t)+
+ sizeof(rx_intr_stat_t);
+ fr_udp_pkt->cblock.result = 0;
+ break;
+
+
+ case FR_FT1_STATUS_CTRL:
+ if(fr_udp_pkt->data[0] == 1) {
+ if(rCount++ != 0 ){
+ fr_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 1;
+ break;
+ }
+ }
+
+ /* Disable FT1 MONITOR STATUS */
+ if(fr_udp_pkt->data[0] == 0) {
+ if( --rCount != 0) {
+ fr_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 1;
+ break;
+ }
+ }
+ goto udp_mgmt_dflt;
+
+
+ default:
+udp_mgmt_dflt:
+ do {
+ memcpy(&mbox->cmd,
+ &fr_udp_pkt->cblock.command,
+ sizeof(fr_cmd_t));
+ if(mbox->cmd.length) {
+ memcpy(&mbox->data,
+ (char *)fr_udp_pkt->data,
+ mbox->cmd.length);
+ }
+
+ err = sdla_exec(mbox) ? mbox->cmd.result :
+ CMD_TIMEOUT;
+ } while (err && c_retry-- && fr_event(card, err, mbox));
+
+ if(!err)
+ chan->drvstats_gen.
+ UDP_PIPE_mgmt_adptr_cmnd_OK ++;
+ else
+ chan->drvstats_gen.
+ UDP_PIPE_mgmt_adptr_cmnd_timeout ++;
+
+ /* copy the result back to our buffer */
+ memcpy(&fr_udp_pkt->cblock.command,
+ &mbox->cmd, sizeof(fr_cmd_t));
+
+ if(mbox->cmd.length) {
+ memcpy(&fr_udp_pkt->data,
+ &mbox->data, mbox->cmd.length);
+ }
+ }
+ }
+
+ /* Fill UDP TTL */
+ fr_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+ len = reply_udp(card->u.f.udp_pkt_data, mbox->cmd.length);
+
+ if(udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+ chan->fr_header_len=2;
+ chan->fr_header[0]=Q922_UI;
+ chan->fr_header[1]=NLPID_IP;
+
+ err = fr_send_data_header(card, dlci, 0, len,
+ card->u.f.udp_pkt_data,chan->fr_header_len);
+ if (err){
+ chan->drvstats_gen.UDP_PIPE_mgmt_adptr_send_passed ++;
+ }else{
+ chan->drvstats_gen.UDP_PIPE_mgmt_adptr_send_failed ++;
+ }
+
+ } else {
+ /* Allocate socket buffer */
+ if((new_skb = dev_alloc_skb(len)) != NULL) {
+
+ /* copy data into new_skb */
+ buf = skb_put(new_skb, len);
+ memcpy(buf, card->u.f.udp_pkt_data, len);
+
+ chan->drvstats_gen.
+ UDP_PIPE_mgmt_passed_to_stack ++;
+ new_skb->dev = dev;
+ new_skb->protocol = htons(ETH_P_IP);
+ new_skb->mac.raw = new_skb->data;
+ netif_rx(new_skb);
+
+ } else {
+ chan->drvstats_gen.UDP_PIPE_mgmt_no_socket ++;
+ printk(KERN_INFO
+ "%s: UDP mgmt cmnd, no socket buffers available!\n",
+ card->devname);
+ }
+ }
+
+ card->u.f.udp_pkt_lgth = 0;
+
+ return 1;
+}
+
+/*==============================================================================
+ * Send Inverse ARP Request
+ */
+
+int send_inarp_request(sdla_t *card, struct net_device *dev)
+{
+ int err=0;
+
+ arphdr_1490_t *ArpPacket;
+ arphdr_fr_t *arphdr;
+ fr_channel_t *chan = dev->priv;
+ struct in_device *in_dev;
+
+ in_dev = dev->ip_ptr;
+
+ if(in_dev != NULL ) {
+
+ ArpPacket = kmalloc(sizeof(arphdr_1490_t) + sizeof(arphdr_fr_t), GFP_ATOMIC);
+ /* SNAP Header indicating ARP */
+ ArpPacket->control = 0x03;
+ ArpPacket->pad = 0x00;
+ ArpPacket->NLPID = 0x80;
+ ArpPacket->OUI[0] = 0;
+ ArpPacket->OUI[1] = 0;
+ ArpPacket->OUI[2] = 0;
+ ArpPacket->PID = 0x0608;
+
+ arphdr = (arphdr_fr_t *)(ArpPacket + 1); // Go to ARP Packet
+
+ /* InARP request */
+ arphdr->ar_hrd = 0x0F00; /* Frame Relay HW type */
+ arphdr->ar_pro = 0x0008; /* IP Protocol */
+ arphdr->ar_hln = 2; /* HW addr length */
+ arphdr->ar_pln = 4; /* IP addr length */
+ arphdr->ar_op = htons(0x08); /* InARP Request */
+ arphdr->ar_sha = 0; /* src HW DLCI - Doesn't matter */
+ if(in_dev->ifa_list != NULL)
+ arphdr->ar_sip = in_dev->ifa_list->ifa_local; /* Local Address */else
+ arphdr->ar_sip = 0;
+ arphdr->ar_tha = 0; /* dst HW DLCI - Doesn't matter */
+ arphdr->ar_tip = 0; /* Remote Address -- what we want */
+
+ err = fr_send(card, chan->dlci, 0, sizeof(arphdr_1490_t) + sizeof(arphdr_fr_t),
+ (void *)ArpPacket);
+
+ if (!err){
+ printk(KERN_INFO "\n%s: Sending InARP request on DLCI %d.\n",
+ card->devname, chan->dlci);
+ clear_bit(ARP_CRIT,&card->wandev.critical);
+ }
+
+ kfree(ArpPacket);
+ }else{
+ printk(KERN_INFO "%s: INARP ERROR: %s doesn't have a local IP address!\n",
+ card->devname,dev->name);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*==============================================================================
+ * Check packet for ARP Type
+ */
+
+int is_arp(void *buf)
+{
+ arphdr_1490_t *arphdr = (arphdr_1490_t *)buf;
+
+ if (arphdr->pad == 0x00 &&
+ arphdr->NLPID == 0x80 &&
+ arphdr->PID == 0x0608)
+ return 1;
+ else return 0;
+}
+
+/*==============================================================================
+ * Process ARP Packet Type
+ */
+
+int process_ARP(arphdr_1490_t *ArpPacket, sdla_t *card, struct net_device* dev)
+{
+
+
+ arphdr_fr_t *arphdr = (arphdr_fr_t *)(ArpPacket + 1); /* Skip header */
+ fr_rx_buf_ctl_t* frbuf = card->rxmb;
+ struct in_device *in_dev;
+ fr_channel_t *chan = dev->priv;
+
+ /* Before we transmit ARP packet, we must check
+ * to see that we are not currently transmitting a
+ * frame (in 'if_send()') and that we are not
+ * already in a 'delayed transmit' state. */
+ if (check_tx_status(card,dev)){
+ if (net_ratelimit()){
+ printk(KERN_INFO "%s: Disabling comminication to process ARP\n",
+ card->devname);
+ }
+ set_bit(ARP_CRIT,&card->wandev.critical);
+ return 0;
+ }
+
+ in_dev = dev->ip_ptr;
+
+ /* Check that IP addresses exist for our network address */
+ if (in_dev == NULL || in_dev->ifa_list == NULL)
+ return -1;
+
+ switch (ntohs(arphdr->ar_op)) {
+
+ case 0x08: // Inverse ARP request -- Send Reply, add route.
+
+ /* Check for valid Address */
+ printk(KERN_INFO "%s: Recvd PtP addr -InArp Req: %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(arphdr->ar_sip));
+
+
+ /* Check that the network address is the same as ours, only
+ * if the netowrk mask is not 255.255.255.255. Otherwise
+ * this check would not make sense */
+
+ if (in_dev->ifa_list->ifa_mask != 0xFFFFFFFF &&
+ (in_dev->ifa_list->ifa_mask & arphdr->ar_sip) !=
+ (in_dev->ifa_list->ifa_mask & in_dev->ifa_list->ifa_local)){
+ printk(KERN_INFO
+ "%s: Invalid PtP address. %u.%u.%u.%u InARP ignored.\n",
+ card->devname,NIPQUAD(arphdr->ar_sip));
+
+ printk(KERN_INFO "%s: mask %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(in_dev->ifa_list->ifa_mask));
+ printk(KERN_INFO "%s: local %u.%u.%u.%u\n",
+ card->devname,NIPQUAD(in_dev->ifa_list->ifa_local));
+ return -1;
+ }
+
+ if (in_dev->ifa_list->ifa_local == arphdr->ar_sip){
+ printk(KERN_INFO
+ "%s: Local addr = PtP addr. InARP ignored.\n",
+ card->devname);
+ return -1;
+ }
+
+ arphdr->ar_op = htons(0x09); /* InARP Reply */
+
+ /* Set addresses */
+ arphdr->ar_tip = arphdr->ar_sip;
+ arphdr->ar_sip = in_dev->ifa_list->ifa_local;
+
+ chan->ip_local = in_dev->ifa_list->ifa_local;
+ chan->ip_remote = arphdr->ar_sip;
+
+ fr_send(card, frbuf->dlci, 0, frbuf->length, (void *)ArpPacket);
+
+ if (test_bit(ARP_CRIT,&card->wandev.critical)){
+ if (net_ratelimit()){
+ printk(KERN_INFO "%s: ARP Processed Enabling Communication!\n",
+ card->devname);
+ }
+ }
+ clear_bit(ARP_CRIT,&card->wandev.critical);
+
+ chan->ip_local = in_dev->ifa_list->ifa_local;
+ chan->ip_remote = arphdr->ar_sip;
+
+ /* Add Route Flag */
+ /* The route will be added in the polling routine so
+ that it is not interrupt context. */
+
+ chan->route_flag = ADD_ROUTE;
+ trigger_fr_poll (dev);
+
+ break;
+
+ case 0x09: // Inverse ARP reply
+
+ /* Check for valid Address */
+ printk(KERN_INFO "%s: Recvd PtP addr %u.%u.%u.%u -InArp Reply\n",
+ card->devname, NIPQUAD(arphdr->ar_sip));
+
+
+ /* Compare network addresses, only if network mask
+ * is not 255.255.255.255 It would not make sense
+ * to perform this test if the mask was all 1's */
+
+ if (in_dev->ifa_list->ifa_mask != 0xffffffff &&
+ (in_dev->ifa_list->ifa_mask & arphdr->ar_sip) !=
+ (in_dev->ifa_list->ifa_mask & in_dev->ifa_list->ifa_local)) {
+
+ printk(KERN_INFO "%s: Invalid PtP address. InARP ignored.\n",
+ card->devname);
+ return -1;
+ }
+
+ /* Make sure that the received IP address is not
+ * the same as our own local address */
+ if (in_dev->ifa_list->ifa_local == arphdr->ar_sip) {
+ printk(KERN_INFO "%s: Local addr = PtP addr. InARP ignored.\n",
+ card->devname);
+ return -1;
+ }
+
+ chan->ip_local = in_dev->ifa_list->ifa_local;
+ chan->ip_remote = arphdr->ar_sip;
+
+ /* Add Route Flag */
+ /* The route will be added in the polling routine so
+ that it is not interrupt context. */
+
+ chan->route_flag = ADD_ROUTE;
+ chan->inarp = INARP_CONFIGURED;
+ trigger_fr_poll(dev);
+
+ break;
+ default:
+ break; // ARP's and RARP's -- Shouldn't happen.
+ }
+
+ return 0;
+}
+
+
+/*============================================================
+ * trigger_fr_arp
+ *
+ * Description:
+ * Add an fr_arp() task into a arp
+ * timer handler for a specific dlci/interface.
+ * This will kick the fr_arp() routine
+ * within the specified time interval.
+ *
+ * Usage:
+ * This timer is used to send ARP requests at
+ * certain time intervals.
+ * Called by an interrupt to request an action
+ * at a later date.
+ */
+
+static void trigger_fr_arp(struct net_device *dev)
+{
+ fr_channel_t* chan = dev->priv;
+
+ mod_timer(&chan->fr_arp_timer, jiffies + chan->inarp_interval * HZ);
+ return;
+}
+
+
+
+/*==============================================================================
+ * ARP Request Action
+ *
+ * This funciton is called by timer interrupt to send an arp request
+ * to the remote end.
+ */
+
+static void fr_arp (unsigned long data)
+{
+ struct net_device *dev = (struct net_device *)data;
+ fr_channel_t *chan = dev->priv;
+ volatile sdla_t *card = chan->card;
+ fr508_flags_t* flags = card->flags;
+
+ /* Send ARP packets for all devs' until
+ * ARP state changes to CONFIGURED */
+
+ if (chan->inarp == INARP_REQUEST &&
+ chan->common.state == WAN_CONNECTED &&
+ card->wandev.state == WAN_CONNECTED){
+ set_bit(0,&chan->inarp_ready);
+ card->u.f.timer_int_enabled |= TMR_INT_ENABLED_ARP;
+ flags->imask |= FR_INTR_TIMER;
+ }
+
+ return;
+}
+
+
+/*==============================================================================
+ * Perform the Interrupt Test by running the READ_CODE_VERSION command MAX_INTR_
+ * TEST_COUNTER times.
+ */
+static int intr_test( sdla_t* card )
+{
+ fr_mbox_t* mb = card->mbox;
+ int err,i;
+
+ err = fr_set_intr_mode(card, FR_INTR_READY, card->wandev.mtu, 0 );
+
+ if (err == CMD_OK) {
+
+ for ( i = 0; i < MAX_INTR_TEST_COUNTER; i++ ) {
+ /* Run command READ_CODE_VERSION */
+ mb->cmd.length = 0;
+ mb->cmd.command = FR_READ_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+ if (err != CMD_OK)
+ fr_event(card, err, mb);
+ }
+
+ } else {
+ return err;
+ }
+
+ err = fr_set_intr_mode( card, 0, card->wandev.mtu, 0 );
+
+ if( err != CMD_OK )
+ return err;
+
+ return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. FPIPE8ND ?
+ */
+static int udp_pkt_type( struct sk_buff *skb, sdla_t* card )
+{
+ fr_udp_pkt_t *fr_udp_pkt = (fr_udp_pkt_t *)skb->data;
+
+ /* Quick HACK */
+
+
+ if((fr_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+ (fr_udp_pkt->ip_pkt.ver_inet_hdr_length == 0x45) &&
+ (fr_udp_pkt->udp_pkt.udp_dst_port ==
+ ntohs(card->wandev.udp_port)) &&
+ (fr_udp_pkt->wp_mgmt.request_reply ==
+ UDPMGMT_REQUEST)) {
+ if(!strncmp(fr_udp_pkt->wp_mgmt.signature,
+ UDPMGMT_FPIPE_SIGNATURE, 8)){
+ return UDP_FPIPE_TYPE;
+ }
+ }
+ return UDP_INVALID_TYPE;
+}
+
+
+/*==============================================================================
+ * Initializes the Statistics values in the fr_channel structure.
+ */
+void init_chan_statistics( fr_channel_t* chan)
+{
+ memset(&chan->drvstats_if_send.if_send_entry, 0,
+ sizeof(if_send_stat_t));
+ memset(&chan->drvstats_rx_intr.rx_intr_no_socket, 0,
+ sizeof(rx_intr_stat_t));
+ memset(&chan->drvstats_gen.UDP_PIPE_mgmt_kmalloc_err, 0,
+ sizeof(pipe_mgmt_stat_t));
+}
+
+/*==============================================================================
+ * Initializes the Statistics values in the Sdla_t structure.
+ */
+void init_global_statistics( sdla_t* card )
+{
+ /* Intialize global statistics for a card */
+ memset(&card->statistics.isr_entry, 0, sizeof(global_stats_t));
+}
+
+static void read_DLCI_IB_mapping( sdla_t* card, fr_channel_t* chan )
+{
+ fr_mbox_t* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ dlci_IB_mapping_t* result;
+ int err, counter, found;
+
+ do {
+ mbox->cmd.command = FR_READ_DLCI_IB_MAPPING;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && fr_event(card, err, mbox));
+
+ if( mbox->cmd.result != 0){
+ printk(KERN_INFO "%s: Read DLCI IB Mapping failed\n",
+ chan->name);
+ }
+
+ counter = mbox->cmd.length / sizeof(dlci_IB_mapping_t);
+ result = (void *)mbox->data;
+
+ found = 0;
+ for (; counter; --counter, ++result) {
+ if ( result->dlci == chan->dlci ) {
+ chan->IB_addr = result->addr_value;
+ if(card->hw.type == SDLA_S514){
+ chan->dlci_int_interface =
+ (void*)(card->hw.dpmbase +
+ chan->IB_addr);
+ }else{
+ chan->dlci_int_interface =
+ (void*)(card->hw.dpmbase +
+ (chan->IB_addr & 0x00001FFF));
+
+ }
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ printk( KERN_INFO "%s: DLCI %d not found by IB MAPPING cmd\n",
+ card->devname, chan->dlci);
+}
+
+
+
+void s508_s514_lock(sdla_t *card, unsigned long *smp_flags)
+{
+ if (card->hw.type != SDLA_S514){
+
+ spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+ }else{
+ spin_lock(&card->u.f.if_send_lock);
+ }
+ return;
+}
+
+
+void s508_s514_unlock(sdla_t *card, unsigned long *smp_flags)
+{
+ if (card->hw.type != SDLA_S514){
+
+ spin_unlock_irqrestore (&card->wandev.lock, *smp_flags);
+ }else{
+ spin_unlock(&card->u.f.if_send_lock);
+ }
+ return;
+}
+
+
+
+/*----------------------------------------------------------------------
+ RECEIVE INTERRUPT: BOTTOM HALF HANDLERS
+ ----------------------------------------------------------------------*/
+
+
+/*========================================================
+ * bh_enqueue
+ *
+ * Description:
+ * Insert a received packet into a circular
+ * rx queue. This packet will be picked up
+ * by fr_bh() and sent up the stack to the
+ * user.
+ *
+ * Usage:
+ * This function is called by rx interrupt,
+ * in API mode.
+ *
+ */
+
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb)
+{
+ /* Check for full */
+ fr_channel_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+
+ if (atomic_read(&chan->bh_buff_used) == MAX_BH_BUFF){
+ ++card->wandev.stats.rx_dropped;
+ dev_kfree_skb_any(skb);
+ return 1;
+ }
+
+ ((bh_data_t *)&chan->bh_head[chan->bh_write])->skb = skb;
+
+ if (chan->bh_write == (MAX_BH_BUFF-1)){
+ chan->bh_write=0;
+ }else{
+ ++chan->bh_write;
+ }
+
+ atomic_inc(&chan->bh_buff_used);
+
+ return 0;
+}
+
+
+/*========================================================
+ * trigger_fr_bh
+ *
+ * Description:
+ * Kick the fr_bh() handler
+ *
+ * Usage:
+ * rx interrupt calls this function during
+ * the API mode.
+ */
+
+static void trigger_fr_bh (fr_channel_t *chan)
+{
+ if (!test_and_set_bit(0,&chan->tq_working)){
+ wanpipe_queue_work(&chan->common.wanpipe_work);
+ }
+}
+
+
+/*========================================================
+ * fr_bh
+ *
+ * Description:
+ * Frame relay receive BH handler.
+ * Dequeue data from the BH circular
+ * buffer and pass it up the API sock.
+ *
+ * Rationale:
+ * This fuction is used to offload the
+ * rx_interrupt during API operation mode.
+ * The fr_bh() function executes for each
+ * dlci/interface.
+ *
+ * Once receive interrupt copies data from the
+ * card into an skb buffer, the skb buffer
+ * is appended to a circular BH buffer.
+ * Then the interrupt kicks fr_bh() to finish the
+ * job at a later time (not within the interrupt).
+ *
+ * Usage:
+ * Interrupts use this to defer a task to
+ * a polling routine.
+ *
+ */
+
+static void fr_bh(struct net_device * dev)
+{
+ fr_channel_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+ struct sk_buff *skb;
+
+ if (atomic_read(&chan->bh_buff_used) == 0){
+ clear_bit(0, &chan->tq_working);
+ return;
+ }
+
+ while (atomic_read(&chan->bh_buff_used)){
+
+ if (chan->common.sk == NULL || chan->common.func == NULL){
+ clear_bit(0, &chan->tq_working);
+ return;
+ }
+
+ skb = ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb;
+
+ if (skb != NULL){
+
+ if (chan->common.sk == NULL || chan->common.func == NULL){
+ ++card->wandev.stats.rx_dropped;
+ ++chan->ifstats.rx_dropped;
+ dev_kfree_skb_any(skb);
+ fr_bh_cleanup(dev);
+ continue;
+ }
+
+ if (chan->common.func(skb,dev,chan->common.sk) != 0){
+ /* Sock full cannot send, queue us for
+ * another try */
+ atomic_set(&chan->common.receive_block,1);
+ return;
+ }else{
+ fr_bh_cleanup(dev);
+ }
+ }else{
+ fr_bh_cleanup(dev);
+ }
+ }
+ clear_bit(0, &chan->tq_working);
+
+ return;
+}
+
+static int fr_bh_cleanup(struct net_device *dev)
+{
+ fr_channel_t* chan = dev->priv;
+
+ ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb = NULL;
+
+ if (chan->bh_read == (MAX_BH_BUFF-1)){
+ chan->bh_read=0;
+ }else{
+ ++chan->bh_read;
+ }
+
+ atomic_dec(&chan->bh_buff_used);
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------
+ POLL BH HANDLERS AND KICK ROUTINES
+ ----------------------------------------------------------------------*/
+
+/*============================================================
+ * trigger_fr_poll
+ *
+ * Description:
+ * Add a fr_poll() task into a tq_scheduler bh handler
+ * for a specific dlci/interface. This will kick
+ * the fr_poll() routine at a later time.
+ *
+ * Usage:
+ * Interrupts use this to defer a taks to
+ * a polling routine.
+ *
+ */
+static void trigger_fr_poll(struct net_device *dev)
+{
+ fr_channel_t* chan = dev->priv;
+ schedule_work(&chan->fr_poll_work);
+ return;
+}
+
+
+/*============================================================
+ * fr_poll
+ *
+ * Rationale:
+ * We cannot manipulate the routing tables, or
+ * ip addresses withing the interrupt. Therefore
+ * we must perform such actons outside an interrupt
+ * at a later time.
+ *
+ * Description:
+ * Frame relay polling routine, responsible for
+ * shutting down interfaces upon disconnect
+ * and adding/removing routes.
+ *
+ * Usage:
+ * This function is executed for each frame relay
+ * dlci/interface through a tq_schedule bottom half.
+ *
+ * trigger_fr_poll() function is used to kick
+ * the fr_poll routine.
+ */
+
+static void fr_poll(struct net_device *dev)
+{
+
+ fr_channel_t* chan;
+ sdla_t *card;
+ u8 check_gateway=0;
+
+ if (!dev || (chan = dev->priv) == NULL)
+ return;
+
+ card = chan->card;
+
+ /* (Re)Configuraiton is in progress, stop what you are
+ * doing and get out */
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ return;
+ }
+
+ switch (chan->common.state){
+
+ case WAN_DISCONNECTED:
+
+ if (test_bit(DYN_OPT_ON,&chan->interface_down) &&
+ !test_bit(DEV_DOWN, &chan->interface_down) &&
+ dev->flags&IFF_UP){
+
+ printk(KERN_INFO "%s: Interface %s is Down.\n",
+ card->devname,dev->name);
+ change_dev_flags(dev,dev->flags&~IFF_UP);
+ set_bit(DEV_DOWN, &chan->interface_down);
+ chan->route_flag = NO_ROUTE;
+
+ }else{
+ if (chan->inarp != INARP_NONE)
+ process_route(dev);
+ }
+ break;
+
+ case WAN_CONNECTED:
+
+ if (test_bit(DYN_OPT_ON,&chan->interface_down) &&
+ test_bit(DEV_DOWN, &chan->interface_down) &&
+ !(dev->flags&IFF_UP)){
+
+ printk(KERN_INFO "%s: Interface %s is Up.\n",
+ card->devname,dev->name);
+
+ change_dev_flags(dev,dev->flags|IFF_UP);
+ clear_bit(DEV_DOWN, &chan->interface_down);
+ check_gateway=1;
+ }
+
+ if (chan->inarp != INARP_NONE){
+ process_route(dev);
+ check_gateway=1;
+ }
+
+ if (chan->gateway && check_gateway)
+ add_gateway(card,dev);
+
+ break;
+
+ }
+
+ return;
+}
+
+/*==============================================================
+ * check_tx_status
+ *
+ * Rationale:
+ * We cannot transmit from an interrupt while
+ * the if_send is transmitting data. Therefore,
+ * we must check whether the tx buffers are
+ * begin used, before we transmit from an
+ * interrupt.
+ *
+ * Description:
+ * Checks whether it's safe to use the transmit
+ * buffers.
+ *
+ * Usage:
+ * ARP and UDP handling routines use this function
+ * because, they need to transmit data during
+ * an interrupt.
+ */
+
+static int check_tx_status(sdla_t *card, struct net_device *dev)
+{
+
+ if (card->hw.type == SDLA_S514){
+ if (test_bit(SEND_CRIT, (void*)&card->wandev.critical) ||
+ test_bit(SEND_TXIRQ_CRIT, (void*)&card->wandev.critical)) {
+ return 1;
+ }
+ }
+
+ if (netif_queue_stopped(dev) || (card->u.f.tx_interrupts_pending))
+ return 1;
+
+ return 0;
+}
+
+/*===============================================================
+ * move_dev_to_next
+ *
+ * Description:
+ * Move the dev pointer to the next location in the
+ * link list. Check if we are at the end of the
+ * list, if so start from the begining.
+ *
+ * Usage:
+ * Timer interrupt uses this function to efficiently
+ * step through the devices that need to send ARP data.
+ *
+ */
+
+struct net_device *move_dev_to_next(sdla_t *card, struct net_device *dev)
+{
+ if (card->wandev.new_if_cnt != 1){
+ if (!*((struct net_device **)dev->priv))
+ return card->wandev.dev;
+ else
+ return *((struct net_device **)dev->priv);
+ }
+ return dev;
+}
+
+/*==============================================================
+ * trigger_config_fr
+ *
+ * Rationale:
+ * All commands must be performed inside of a
+ * interrupt.
+ *
+ * Description:
+ * Kick the config_fr() routine throught the
+ * timer interrupt.
+ */
+
+
+static void trigger_config_fr (sdla_t *card)
+{
+ fr508_flags_t* flags = card->flags;
+
+ card->u.f.timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+ flags->imask |= FR_INTR_TIMER;
+}
+
+
+/*==============================================================
+ * config_fr
+ *
+ * Rationale:
+ * All commands must be performed inside of a
+ * interrupt.
+ &
+ * Description:
+ * Configure a DLCI. This function is executed
+ * by a timer_interrupt. The if_open() function
+ * triggers it.
+ *
+ * Usage:
+ * new_if() collects all data necessary to
+ * configure the DLCI. It sets the chan->dlci_ready
+ * bit. When the if_open() function is executed
+ * it checks this bit, and if its set it triggers
+ * the timer interrupt to execute the config_fr()
+ * function.
+ */
+
+static void config_fr (sdla_t *card)
+{
+ struct net_device *dev;
+ fr_channel_t *chan;
+
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)) {
+
+ if ((chan=dev->priv) == NULL)
+ continue;
+
+ if (!test_bit(0,&chan->config_dlci))
+ continue;
+
+ clear_bit(0,&chan->config_dlci);
+
+ /* If signalling is set to NO, then setup
+ * DLCI addresses right away. Don't have to wait for
+ * link to connect.
+ */
+ if (card->wandev.signalling == WANOPT_NO){
+ printk(KERN_INFO "%s: Signalling set to NO: Mapping DLCI's\n",
+ card->wandev.name);
+ if (fr_init_dlci(card,chan)){
+ printk(KERN_INFO "%s: ERROR: Failed to configure DLCI %i !\n",
+ card->devname, chan->dlci);
+ return;
+ }
+ }
+
+ if (card->wandev.station == WANOPT_CPE) {
+
+ update_chan_state(dev);
+
+ /* CPE: issue full status enquiry */
+ fr_issue_isf(card, FR_ISF_FSE);
+
+ } else {
+ /* FR switch: activate DLCI(s) */
+
+ /* For Switch emulation we have to ADD and ACTIVATE
+ * the DLCI(s) that were configured with the SET_DLCI_
+ * CONFIGURATION command. Add and Activate will fail if
+ * DLCI specified is not included in the list.
+ *
+ * Also If_open is called once for each interface. But
+ * it does not get in here for all the interface. So
+ * we have to pass the entire list of DLCI(s) to add
+ * activate routines.
+ */
+
+ if (!check_dlci_config (card, chan)){
+ fr_add_dlci(card, chan->dlci);
+ fr_activate_dlci(card, chan->dlci);
+ }
+ }
+
+ card->u.f.dlci_to_dev_map[chan->dlci] = dev;
+ }
+ return;
+}
+
+
+/*==============================================================
+ * config_fr
+ *
+ * Rationale:
+ * All commands must be executed during an interrupt.
+ *
+ * Description:
+ * Trigger uncofig_fr() function through
+ * the timer interrupt.
+ *
+ */
+
+static void trigger_unconfig_fr(struct net_device *dev)
+{
+ fr_channel_t *chan = dev->priv;
+ volatile sdla_t *card = chan->card;
+ u32 timeout;
+ fr508_flags_t* flags = card->flags;
+ int reset_critical=0;
+
+ if (test_bit(PERI_CRIT,(void*)&card->wandev.critical)){
+ clear_bit(PERI_CRIT,(void*)&card->wandev.critical);
+ reset_critical=1;
+ }
+
+ /* run unconfig_dlci() function
+ * throught the timer interrupt */
+ set_bit(0,(void*)&chan->unconfig_dlci);
+ card->u.f.timer_int_enabled |= TMR_INT_ENABLED_UNCONFIG;
+ flags->imask |= FR_INTR_TIMER;
+
+ /* Wait for the command to complete */
+ timeout = jiffies;
+ for(;;) {
+
+ if(!(card->u.f.timer_int_enabled & TMR_INT_ENABLED_UNCONFIG))
+ break;
+
+ if ((jiffies - timeout) > (1 * HZ)){
+ card->u.f.timer_int_enabled &= ~TMR_INT_ENABLED_UNCONFIG;
+ printk(KERN_INFO "%s: Failed to delete DLCI %i\n",
+ card->devname,chan->dlci);
+ break;
+ }
+ }
+
+ if (reset_critical){
+ set_bit(PERI_CRIT,(void*)&card->wandev.critical);
+ }
+}
+
+/*==============================================================
+ * unconfig_fr
+ *
+ * Rationale:
+ * All commands must be executed during an interrupt.
+ *
+ * Description:
+ * Remove the dlci from firmware.
+ * This funciton is used in NODE shutdown.
+ */
+
+static void unconfig_fr (sdla_t *card)
+{
+ struct net_device *dev;
+ fr_channel_t *chan;
+
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)){
+
+ if ((chan=dev->priv) == NULL)
+ continue;
+
+ if (!test_bit(0,&chan->unconfig_dlci))
+ continue;
+
+ clear_bit(0,&chan->unconfig_dlci);
+
+ if (card->wandev.station == WANOPT_NODE){
+ printk(KERN_INFO "%s: Unconfiguring DLCI %i\n",
+ card->devname,chan->dlci);
+ fr_delete_dlci(card,chan->dlci);
+ }
+ card->u.f.dlci_to_dev_map[chan->dlci] = NULL;
+ }
+}
+
+static int setup_fr_header(struct sk_buff **skb_orig, struct net_device* dev,
+ char op_mode)
+{
+ struct sk_buff *skb = *skb_orig;
+ fr_channel_t *chan=dev->priv;
+
+ if (op_mode == WANPIPE){
+
+ chan->fr_header[0]=Q922_UI;
+
+ switch (htons(skb->protocol)){
+
+ case ETH_P_IP:
+ chan->fr_header[1]=NLPID_IP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 2;
+ }
+
+ /* If we are in bridging mode, we must apply
+ * an Ethernet header */
+ if (op_mode == BRIDGE || op_mode == BRIDGE_NODE){
+
+
+ /* Encapsulate the packet as a bridged Ethernet frame. */
+#ifdef DEBUG
+ printk(KERN_INFO "%s: encapsulating skb for frame relay\n",
+ dev->name);
+#endif
+
+ chan->fr_header[0] = 0x03;
+ chan->fr_header[1] = 0x00;
+ chan->fr_header[2] = 0x80;
+ chan->fr_header[3] = 0x00;
+ chan->fr_header[4] = 0x80;
+ chan->fr_header[5] = 0xC2;
+ chan->fr_header[6] = 0x00;
+ chan->fr_header[7] = 0x07;
+
+ /* Yuck. */
+ skb->protocol = ETH_P_802_3;
+ return 8;
+
+ }
+
+ return 0;
+}
+
+
+static int check_dlci_config (sdla_t *card, fr_channel_t *chan)
+{
+ fr_mbox_t* mbox = card->mbox;
+ int err=0;
+ fr_conf_t *conf=NULL;
+ unsigned short dlci_num = chan->dlci;
+ int dlci_offset=0;
+ struct net_device *dev = NULL;
+
+ mbox->cmd.command = FR_READ_CONFIG;
+ mbox->cmd.length = 0;
+ mbox->cmd.dlci = dlci_num;
+
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ if (err == CMD_OK){
+ return 0;
+ }
+
+ for (dev = card->wandev.dev; dev;
+ dev=*((struct net_device **)dev->priv))
+ set_chan_state(dev,WAN_DISCONNECTED);
+
+ printk(KERN_INFO "DLCI %i Not configured, configuring\n",dlci_num);
+
+ mbox->cmd.command = FR_COMM_DISABLE;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err != CMD_OK){
+ fr_event(card, err, mbox);
+ return 2;
+ }
+
+ printk(KERN_INFO "Disabled Communications \n");
+
+ mbox->cmd.command = FR_READ_CONFIG;
+ mbox->cmd.length = 0;
+ mbox->cmd.dlci = 0;
+
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK){
+ fr_event(card, err, mbox);
+ return 2;
+ }
+
+ conf = (fr_conf_t *)mbox->data;
+
+ dlci_offset=0;
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)) {
+ fr_channel_t *chan_tmp = dev->priv;
+ conf->dlci[dlci_offset] = chan_tmp->dlci;
+ dlci_offset++;
+ }
+
+ printk(KERN_INFO "Got Fr configuration Buffer Length is %x Dlci %i Dlci Off %i\n",
+ mbox->cmd.length,
+ mbox->cmd.length > 0x20 ? conf->dlci[0] : -1,
+ dlci_offset );
+
+ mbox->cmd.length = 0x20 + dlci_offset*2;
+
+ mbox->cmd.command = FR_SET_CONFIG;
+ mbox->cmd.dlci = 0;
+
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK){
+ fr_event(card, err, mbox);
+ return 2;
+ }
+
+ initialize_rx_tx_buffers (card);
+
+
+ printk(KERN_INFO "Configuraiton Succeded for new DLCI %i\n",dlci_num);
+
+ if (fr_comm_enable (card)){
+ return 2;
+ }
+
+ printk(KERN_INFO "Enabling Communications \n");
+
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)) {
+ fr_channel_t *chan_tmp = dev->priv;
+ fr_init_dlci(card,chan_tmp);
+ fr_add_dlci(card, chan_tmp->dlci);
+ fr_activate_dlci(card, chan_tmp->dlci);
+ }
+
+ printk(KERN_INFO "END OF CONFIGURAITON %i\n",dlci_num);
+
+ return 1;
+}
+
+static void initialize_rx_tx_buffers (sdla_t *card)
+{
+ fr_buf_info_t* buf_info;
+
+ if (card->hw.type == SDLA_S514) {
+
+ buf_info = (void*)(card->hw.dpmbase + FR_MB_VECTOR +
+ FR508_RXBC_OFFS);
+
+ card->rxmb = (void*)(buf_info->rse_next + card->hw.dpmbase);
+
+ card->u.f.rxmb_base =
+ (void*)(buf_info->rse_base + card->hw.dpmbase);
+
+ card->u.f.rxmb_last =
+ (void*)(buf_info->rse_base +
+ (buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) +
+ card->hw.dpmbase);
+ }else{
+ buf_info = (void*)(card->hw.dpmbase + FR508_RXBC_OFFS);
+
+ card->rxmb = (void*)(buf_info->rse_next -
+ FR_MB_VECTOR + card->hw.dpmbase);
+
+ card->u.f.rxmb_base =
+ (void*)(buf_info->rse_base -
+ FR_MB_VECTOR + card->hw.dpmbase);
+
+ card->u.f.rxmb_last =
+ (void*)(buf_info->rse_base +
+ (buf_info->rse_num - 1) * sizeof(fr_rx_buf_ctl_t) -
+ FR_MB_VECTOR + card->hw.dpmbase);
+ }
+
+ card->u.f.rx_base = buf_info->buf_base;
+ card->u.f.rx_top = buf_info->buf_top;
+
+ card->u.f.tx_interrupts_pending = 0;
+
+ return;
+}
+
+
+
+MODULE_LICENSE("GPL");
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdla_ft1.c b/drivers/net/wan/sdla_ft1.c
new file mode 100644
index 00000000000..5e3124856eb
--- /dev/null
+++ b/drivers/net/wan/sdla_ft1.c
@@ -0,0 +1,344 @@
+/*****************************************************************************
+* sdla_chdlc.c WANPIPE(tm) Multiprotocol WAN Link Driver. Cisco HDLC module.
+*
+* Authors: Nenad Corbic <ncorbic@sangoma.com>
+* Gideon Hack
+*
+* Copyright: (c) 1995-1999 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Sep 30, 1999 Nenad Corbic Fixed dynamic IP and route setup.
+* Sep 23, 1999 Nenad Corbic Added SMP support, fixed tracing
+* Sep 13, 1999 Nenad Corbic Split up Port 0 and 1 into separate devices.
+* Jun 02, 1999 Gideon Hack Added support for the S514 adapter.
+* Oct 30, 1998 Jaspreet Singh Added Support for CHDLC API (HDLC STREAMING).
+* Oct 28, 1998 Jaspreet Singh Added Support for Dual Port CHDLC.
+* Aug 07, 1998 David Fong Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+#include <linux/if_arp.h> /* ARPHRD_* defines */
+
+#include <linux/inetdevice.h>
+#include <asm/uaccess.h>
+
+#include <linux/in.h> /* sockaddr_in */
+#include <linux/inet.h>
+#include <linux/if.h>
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <linux/sdlapci.h>
+#include <asm/io.h>
+
+#include <linux/sdla_chdlc.h> /* CHDLC firmware API definitions */
+
+/****** Defines & Macros ****************************************************/
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP 0x0001
+#define TMR_INT_ENABLED_UPDATE 0x0002
+
+#define CHDLC_DFLT_DATA_LEN 1500 /* default MTU */
+#define CHDLC_HDR_LEN 1
+
+#define IFF_POINTTOPOINT 0x10
+
+#define WANPIPE 0x00
+#define API 0x01
+#define CHDLC_API 0x01
+
+#define PORT(x) (x == 0 ? "PRIMARY" : "SECONDARY" )
+
+
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following
+ * structure will incorporate the card structure along with CHDLC specific data
+ */
+
+typedef struct chdlc_private_area
+{
+ struct net_device *slave;
+ sdla_t *card;
+ int TracingEnabled; /* For enabling Tracing */
+ unsigned long curr_trace_addr; /* Used for Tracing */
+ unsigned long start_trace_addr;
+ unsigned long end_trace_addr;
+ unsigned long base_addr_trace_buffer;
+ unsigned long end_addr_trace_buffer;
+ unsigned short number_trace_elements;
+ unsigned available_buffer_space;
+ unsigned long router_start_time;
+ unsigned char route_status;
+ unsigned char route_removed;
+ unsigned long tick_counter; /* For 5s timeout counter */
+ unsigned long router_up_time;
+ u32 IP_address; /* IP addressing */
+ u32 IP_netmask;
+ unsigned char mc; /* Mulitcast support on/off */
+ unsigned short udp_pkt_lgth; /* udp packet processing */
+ char udp_pkt_src;
+ char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+ unsigned short timer_int_enabled;
+ char update_comms_stats; /* updating comms stats */
+ //FIXME: add driver stats as per frame relay!
+
+} chdlc_private_area_t;
+
+/* Route Status options */
+#define NO_ROUTE 0x00
+#define ADD_ROUTE 0x01
+#define ROUTE_ADDED 0x02
+#define REMOVE_ROUTE 0x03
+
+
+/****** Function Prototypes *************************************************/
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int wpft1_exec (struct sdla *card, void *u_cmd, void *u_data);
+static int chdlc_read_version (sdla_t* card, char* str);
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb);
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Cisco HDLC protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup. At this
+ * point adapter is completely initialized and firmware is running.
+ * o read firmware version (to make sure it's alive)
+ * o configure adapter
+ * o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return: 0 o.k.
+ * < 0 failure.
+ */
+int wpft1_init (sdla_t* card, wandev_conf_t* conf)
+{
+ unsigned char port_num;
+ int err;
+
+ union
+ {
+ char str[80];
+ } u;
+ volatile CHDLC_MAILBOX_STRUCT* mb;
+ CHDLC_MAILBOX_STRUCT* mb1;
+ unsigned long timeout;
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_CHDLC) {
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id);
+ return -EINVAL;
+ }
+
+ /* Use primary port */
+ card->u.c.comm_port = 0;
+
+
+ /* Initialize protocol-specific fields */
+ if(card->hw.type != SDLA_S514){
+ card->mbox = (void *) card->hw.dpmbase;
+ }else{
+ card->mbox = (void *) card->hw.dpmbase + PRI_BASE_ADDR_MB_STRUCT;
+ }
+
+ mb = mb1 = card->mbox;
+
+ if (!card->configured){
+
+ /* The board will place an 'I' in the return code to indicate that it is
+ ready to accept commands. We expect this to be completed in less
+ than 1 second. */
+
+ timeout = jiffies;
+ while (mb->return_code != 'I') /* Wait 1s for board to initialize */
+ if ((jiffies - timeout) > 1*HZ) break;
+
+ if (mb->return_code != 'I') {
+ printk(KERN_INFO
+ "%s: Initialization not completed by adapter\n",
+ card->devname);
+ printk(KERN_INFO "Please contact Sangoma representative.\n");
+ return -EIO;
+ }
+ }
+
+ /* Read firmware version. Note that when adapter initializes, it
+ * clears the mailbox, so it may appear that the first command was
+ * executed successfully when in fact it was merely erased. To work
+ * around this, we execute the first command twice.
+ */
+
+ if (chdlc_read_version(card, u.str))
+ return -EIO;
+
+ printk(KERN_INFO "%s: Running FT1 Configuration firmware v%s\n",
+ card->devname, u.str);
+
+ card->isr = NULL;
+ card->poll = NULL;
+ card->exec = &wpft1_exec;
+ card->wandev.update = NULL;
+ card->wandev.new_if = NULL;
+ card->wandev.del_if = NULL;
+ card->wandev.state = WAN_DUALPORT;
+ card->wandev.udp_port = conf->udp_port;
+
+ card->wandev.new_if_cnt = 0;
+
+ /* This is for the ports link state */
+ card->u.c.state = WAN_DISCONNECTED;
+
+ /* reset the number of times the 'update()' proc has been called */
+ card->u.c.update_call_count = 0;
+
+ card->wandev.ttl = 0x7F;
+ card->wandev.interface = 0;
+
+ card->wandev.clocking = 0;
+
+ port_num = card->u.c.comm_port;
+
+ /* Setup Port Bps */
+
+ card->wandev.bps = 0;
+
+ card->wandev.mtu = MIN_LGTH_CHDLC_DATA_CFG;
+
+ /* Set up the interrupt status area */
+ /* Read the CHDLC Configuration and obtain:
+ * Ptr to shared memory infor struct
+ * Use this pointer to calculate the value of card->u.c.flags !
+ */
+ mb1->buffer_length = 0;
+ mb1->command = READ_CHDLC_CONFIGURATION;
+ err = sdla_exec(mb1) ? mb1->return_code : CMD_TIMEOUT;
+ if(err != COMMAND_OK) {
+ chdlc_error(card, err, mb1);
+ return -EIO;
+ }
+
+ if(card->hw.type == SDLA_S514){
+ card->u.c.flags = (void *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+ ptr_shared_mem_info_struct));
+ }else{
+ card->u.c.flags = (void *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+ ptr_shared_mem_info_struct % SDLA_WINDOWSIZE));
+ }
+
+ card->wandev.state = WAN_FT1_READY;
+ printk(KERN_INFO "%s: FT1 Config Ready !\n",card->devname);
+
+ return 0;
+}
+
+static int wpft1_exec(sdla_t *card, void *u_cmd, void *u_data)
+{
+ CHDLC_MAILBOX_STRUCT* mbox = card->mbox;
+ int len;
+
+ if (copy_from_user((void*)&mbox->command, u_cmd, sizeof(ft1_exec_cmd_t))){
+ return -EFAULT;
+ }
+
+ len = mbox->buffer_length;
+
+ if (len) {
+ if( copy_from_user((void*)&mbox->data, u_data, len)){
+ return -EFAULT;
+ }
+ }
+
+ /* execute command */
+ if (!sdla_exec(mbox)){
+ return -EIO;
+ }
+
+ /* return result */
+ if( copy_to_user(u_cmd, (void*)&mbox->command, sizeof(ft1_exec_cmd_t))){
+ return -EFAULT;
+ }
+
+ len = mbox->buffer_length;
+
+ if (len && u_data && copy_to_user(u_data, (void*)&mbox->data, len)){
+ return -EFAULT;
+ }
+
+ return 0;
+
+}
+
+/*============================================================================
+ * Read firmware code version.
+ * Put code version as ASCII string in str.
+ */
+static int chdlc_read_version (sdla_t* card, char* str)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int len;
+ char err;
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if(err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ }
+ else if (str) { /* is not null */
+ len = mb->buffer_length;
+ memcpy(str, mb->data, len);
+ str[len] = '\0';
+ }
+ return (err);
+}
+
+/*============================================================================
+ * Firmware error handler.
+ * This routine is called whenever firmware command returns non-zero
+ * return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb)
+{
+ unsigned cmd = mb->command;
+
+ switch (err) {
+
+ case CMD_TIMEOUT:
+ printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+ card->devname, cmd);
+ break;
+
+ case S514_BOTH_PORTS_SAME_CLK_MODE:
+ if(cmd == SET_CHDLC_CONFIGURATION) {
+ printk(KERN_INFO
+ "%s: Configure both ports for the same clock source\n",
+ card->devname);
+ break;
+ }
+
+ default:
+ printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n",
+ card->devname, cmd, err);
+ }
+
+ return 0;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/sdla_ppp.c b/drivers/net/wan/sdla_ppp.c
new file mode 100644
index 00000000000..1761cb68ab4
--- /dev/null
+++ b/drivers/net/wan/sdla_ppp.c
@@ -0,0 +1,3429 @@
+/*****************************************************************************
+* sdla_ppp.c WANPIPE(tm) Multiprotocol WAN Link Driver. PPP module.
+*
+* Author: Nenad Corbic <ncorbic@sangoma.com>
+*
+* Copyright: (c) 1995-2001 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Feb 28, 2001 Nenad Corbic o Updated if_tx_timeout() routine for
+* 2.4.X kernels.
+* Nov 29, 2000 Nenad Corbic o Added the 2.4.x kernel support:
+* get_ip_address() function has moved
+* into the ppp_poll() routine. It cannot
+* be called from an interrupt.
+* Nov 07, 2000 Nenad Corbic o Added security features for UDP debugging:
+* Deny all and specify allowed requests.
+* May 02, 2000 Nenad Corbic o Added the dynamic interface shutdown
+* option. When the link goes down, the
+* network interface IFF_UP flag is reset.
+* Mar 06, 2000 Nenad Corbic o Bug Fix: corrupted mbox recovery.
+* Feb 25, 2000 Nenad Corbic o Fixed the FT1 UDP debugger problem.
+* Feb 09, 2000 Nenad Coribc o Shutdown bug fix. update() was called
+* with NULL dev pointer: no check.
+* Jan 24, 2000 Nenad Corbic o Disabled use of CMD complete inter.
+* Dev 15, 1999 Nenad Corbic o Fixed up header files for 2.0.X kernels
+* Oct 25, 1999 Nenad Corbic o Support for 2.0.X kernels
+* Moved dynamic route processing into
+* a polling routine.
+* Oct 07, 1999 Nenad Corbic o Support for S514 PCI card.
+* Gideon Hack o UPD and Updates executed using timer interrupt
+* Sep 10, 1999 Nenad Corbic o Fixed up the /proc statistics
+* Jul 20, 1999 Nenad Corbic o Remove the polling routines and use
+* interrupts instead.
+* Sep 17, 1998 Jaspreet Singh o Updates for 2.2.X Kernels.
+* Aug 13, 1998 Jaspreet Singh o Improved Line Tracing.
+* Jun 22, 1998 David Fong o Added remote IP address assignment
+* Mar 15, 1998 Alan Cox o 2.1.8x basic port.
+* Apr 16, 1998 Jaspreet Singh o using htons() for the IPX protocol.
+* Dec 09, 1997 Jaspreet Singh o Added PAP and CHAP.
+* o Implemented new routines like
+* ppp_set_inbnd_auth(), ppp_set_outbnd_auth(),
+* tokenize() and strstrip().
+* Nov 27, 1997 Jaspreet Singh o Added protection against enabling of irqs
+* while they have been disabled.
+* Nov 24, 1997 Jaspreet Singh o Fixed another RACE condition caused by
+* disabling and enabling of irqs.
+* o Added new counters for stats on disable/enable
+* IRQs.
+* Nov 10, 1997 Jaspreet Singh o Initialized 'skb->mac.raw' to 'skb->data'
+* before every netif_rx().
+* o Free up the device structure in del_if().
+* Nov 07, 1997 Jaspreet Singh o Changed the delay to zero for Line tracing
+* command.
+* Oct 20, 1997 Jaspreet Singh o Added hooks in for Router UP time.
+* Oct 16, 1997 Jaspreet Singh o The critical flag is used to maintain flow
+* control by avoiding RACE conditions. The
+* cli() and restore_flags() are taken out.
+* A new structure, "ppp_private_area", is added
+* to provide Driver Statistics.
+* Jul 21, 1997 Jaspreet Singh o Protected calls to sdla_peek() by adding
+* save_flags(), cli() and restore_flags().
+* Jul 07, 1997 Jaspreet Singh o Added configurable TTL for UDP packets
+* o Added ability to discard mulitcast and
+* broacast source addressed packets.
+* Jun 27, 1997 Jaspreet Singh o Added FT1 monitor capabilities
+* New case (0x25) statement in if_send routine.
+* Added a global variable rCount to keep track
+* of FT1 status enabled on the board.
+* May 22, 1997 Jaspreet Singh o Added change in the PPP_SET_CONFIG command for
+* 508 card to reflect changes in the new
+* ppp508.sfm for supporting:continous transmission
+* of Configure-Request packets without receiving a
+* reply
+* OR-ed 0x300 to conf_flags
+* o Changed connect_tmout from 900 to 0
+* May 21, 1997 Jaspreet Singh o Fixed UDP Management for multiple boards
+* Apr 25, 1997 Farhan Thawar o added UDP Management stuff
+* Mar 11, 1997 Farhan Thawar Version 3.1.1
+* o fixed (+1) bug in rx_intr()
+* o changed if_send() to return 0 if
+* wandev.critical() is true
+* o free socket buffer in if_send() if
+* returning 0
+* Jan 15, 1997 Gene Kozin Version 3.1.0
+* o implemented exec() entry point
+* Jan 06, 1997 Gene Kozin Initial version.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+#include <linux/if_arp.h> /* ARPHRD_* defines */
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <linux/in.h> /* sockaddr_in */
+
+
+#include <asm/uaccess.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+
+#include <linux/if.h>
+#include <linux/sdla_ppp.h> /* PPP firmware API definitions */
+#include <linux/sdlasfm.h> /* S514 Type Definition */
+/****** Defines & Macros ****************************************************/
+
+#define PPP_DFLT_MTU 1500 /* default MTU */
+#define PPP_MAX_MTU 4000 /* maximum MTU */
+#define PPP_HDR_LEN 1
+
+#define MAX_IP_ERRORS 100
+
+#define CONNECT_TIMEOUT (90*HZ) /* link connection timeout */
+#define HOLD_DOWN_TIME (5*HZ) /* link hold down time : Changed from 30 to 5 */
+
+/* For handle_IPXWAN() */
+#define CVHexToAscii(b) (((unsigned char)(b) > (unsigned char)9) ? ((unsigned char)'A' + ((unsigned char)(b) - (unsigned char)10)) : ((unsigned char)'0' + (unsigned char)(b)))
+
+/* Macro for enabling/disabling debugging comments */
+//#define NEX_DEBUG
+#ifdef NEX_DEBUG
+#define NEX_PRINTK(format, a...) printk(format, ## a)
+#else
+#define NEX_PRINTK(format, a...)
+#endif /* NEX_DEBUG */
+
+#define DCD(a) ( a & 0x08 ? "HIGH" : "LOW" )
+#define CTS(a) ( a & 0x20 ? "HIGH" : "LOW" )
+#define LCP(a) ( a == 0x09 ? "OPEN" : "CLOSED" )
+#define IP(a) ( a == 0x09 ? "ENABLED" : "DISABLED" )
+
+#define TMR_INT_ENABLED_UPDATE 0x01
+#define TMR_INT_ENABLED_PPP_EVENT 0x02
+#define TMR_INT_ENABLED_UDP 0x04
+#define TMR_INT_ENABLED_CONFIG 0x20
+
+/* Set Configuraton Command Definitions */
+#define PERCENT_TX_BUFF 60
+#define TIME_BETWEEN_CONF_REQ 30
+#define TIME_BETWEEN_PAP_CHAP_REQ 30
+#define WAIT_PAP_CHAP_WITHOUT_REPLY 300
+#define WAIT_AFTER_DCD_CTS_LOW 5
+#define TIME_DCD_CTS_LOW_AFTER_LNK_DOWN 10
+#define WAIT_DCD_HIGH_AFTER_ENABLE_COMM 900
+#define MAX_CONF_REQ_WITHOUT_REPLY 10
+#define MAX_TERM_REQ_WITHOUT_REPLY 2
+#define NUM_CONF_NAK_WITHOUT_REPLY 5
+#define NUM_AUTH_REQ_WITHOUT_REPLY 10
+
+#define END_OFFSET 0x1F0
+
+
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following
+ * structure will incorporate the card structure along with PPP specific data
+ */
+
+typedef struct ppp_private_area
+{
+ struct net_device *slave;
+ sdla_t* card;
+ unsigned long router_start_time; /*router start time in sec */
+ unsigned long tick_counter; /*used for 5 second counter*/
+ unsigned mc; /*multicast support on or off*/
+ unsigned char enable_IPX;
+ unsigned long network_number;
+ unsigned char pap;
+ unsigned char chap;
+ unsigned char sysname[31]; /* system name for in-bnd auth*/
+ unsigned char userid[511]; /* list of user ids */
+ unsigned char passwd[511]; /* list of passwords */
+ unsigned protocol; /* SKB Protocol */
+ u32 ip_local; /* Local IP Address */
+ u32 ip_remote; /* remote IP Address */
+
+ u32 ip_local_tmp;
+ u32 ip_remote_tmp;
+
+ unsigned char timer_int_enabled; /* Who enabled the timer inter*/
+ unsigned char update_comms_stats; /* Used by update function */
+ unsigned long curr_trace_addr; /* Trace information */
+ unsigned long start_trace_addr;
+ unsigned long end_trace_addr;
+
+ unsigned char interface_down; /* Brind down interface when channel
+ goes down */
+ unsigned long config_wait_timeout; /* After if_open() if in dynamic if mode,
+ wait a few seconds before configuring */
+
+ unsigned short udp_pkt_lgth;
+ char udp_pkt_src;
+ char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+
+ /* PPP specific statistics */
+
+ if_send_stat_t if_send_stat;
+ rx_intr_stat_t rx_intr_stat;
+ pipe_mgmt_stat_t pipe_mgmt_stat;
+
+ unsigned long router_up_time;
+
+ /* Polling work queue entry. Each interface
+ * has its own work queue entry, which is used
+ * to defer events from the interrupt */
+ struct work_struct poll_work;
+ struct timer_list poll_delay_timer;
+
+ u8 gateway;
+ u8 config_ppp;
+ u8 ip_error;
+
+}ppp_private_area_t;
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/****** Function Prototypes *************************************************/
+
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device *wandev);
+static int new_if(struct wan_device *wandev, struct net_device *dev,
+ wanif_conf_t *conf);
+static int del_if(struct wan_device *wandev, struct net_device *dev);
+
+/* WANPIPE-specific entry points */
+static int wpp_exec (struct sdla *card, void *u_cmd, void *u_data);
+
+/* Network device interface */
+static int if_init(struct net_device *dev);
+static int if_open(struct net_device *dev);
+static int if_close(struct net_device *dev);
+static int if_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type,
+ void *daddr, void *saddr, unsigned len);
+
+static void if_tx_timeout(struct net_device *dev);
+
+static int if_rebuild_hdr(struct sk_buff *skb);
+static struct net_device_stats *if_stats(struct net_device *dev);
+static int if_send(struct sk_buff *skb, struct net_device *dev);
+
+
+/* PPP firmware interface functions */
+static int ppp_read_version(sdla_t *card, char *str);
+static int ppp_set_outbnd_auth(sdla_t *card, ppp_private_area_t *ppp_priv_area);
+static int ppp_set_inbnd_auth(sdla_t *card, ppp_private_area_t *ppp_priv_area);
+static int ppp_configure(sdla_t *card, void *data);
+static int ppp_set_intr_mode(sdla_t *card, unsigned char mode);
+static int ppp_comm_enable(sdla_t *card);
+static int ppp_comm_disable(sdla_t *card);
+static int ppp_comm_disable_shutdown(sdla_t *card);
+static int ppp_get_err_stats(sdla_t *card);
+static int ppp_send(sdla_t *card, void *data, unsigned len, unsigned proto);
+static int ppp_error(sdla_t *card, int err, ppp_mbox_t *mb);
+
+static void wpp_isr(sdla_t *card);
+static void rx_intr(sdla_t *card);
+static void event_intr(sdla_t *card);
+static void timer_intr(sdla_t *card);
+
+/* Background polling routines */
+static void process_route(sdla_t *card);
+static void retrigger_comm(sdla_t *card);
+
+/* Miscellaneous functions */
+static int read_info( sdla_t *card );
+static int read_connection_info (sdla_t *card);
+static void remove_route( sdla_t *card );
+static int config508(struct net_device *dev, sdla_t *card);
+static void show_disc_cause(sdla_t * card, unsigned cause);
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+static void process_udp_mgmt_pkt(sdla_t *card, struct net_device *dev,
+ ppp_private_area_t *ppp_priv_area);
+static void init_ppp_tx_rx_buff( sdla_t *card );
+static int intr_test( sdla_t *card );
+static int udp_pkt_type( struct sk_buff *skb , sdla_t *card);
+static void init_ppp_priv_struct( ppp_private_area_t *ppp_priv_area);
+static void init_global_statistics( sdla_t *card );
+static int tokenize(char *str, char **tokens);
+static char* strstrip(char *str, char *s);
+static int chk_bcast_mcast_addr(sdla_t* card, struct net_device* dev,
+ struct sk_buff *skb);
+
+static int config_ppp (sdla_t *);
+static void ppp_poll(struct net_device *dev);
+static void trigger_ppp_poll(struct net_device *dev);
+static void ppp_poll_delay (unsigned long dev_ptr);
+
+
+static int Read_connection_info;
+static int Intr_test_counter;
+static unsigned short available_buffer_space;
+
+
+/* IPX functions */
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number,
+ unsigned char incoming);
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname, unsigned char enable_PX,
+ unsigned long network_number, unsigned short proto);
+
+/* Lock Functions */
+static void s508_lock (sdla_t *card, unsigned long *smp_flags);
+static void s508_unlock (sdla_t *card, unsigned long *smp_flags);
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, struct net_device* dev,
+ ppp_private_area_t* ppp_priv_area );
+static unsigned short calc_checksum (char *data, int len);
+static void disable_comm (sdla_t *card);
+static int detect_and_fix_tx_bug (sdla_t *card);
+
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * PPP protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup. At this
+ * point adapter is completely initialized and firmware is running.
+ * o read firmware version (to make sure it's alive)
+ * o configure adapter
+ * o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return: 0 o.k.
+ * < 0 failure.
+ */
+int wpp_init(sdla_t *card, wandev_conf_t *conf)
+{
+ ppp_flags_t *flags;
+ union
+ {
+ char str[80];
+ } u;
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_PPP) {
+
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id);
+ return -EINVAL;
+
+ }
+
+ /* Initialize miscellaneous pointers to structures on the adapter */
+ switch (card->hw.type) {
+
+ case SDLA_S508:
+ card->mbox =(void*)(card->hw.dpmbase + PPP508_MB_OFFS);
+ card->flags=(void*)(card->hw.dpmbase + PPP508_FLG_OFFS);
+ break;
+
+ case SDLA_S514:
+ card->mbox =(void*)(card->hw.dpmbase + PPP514_MB_OFFS);
+ card->flags=(void*)(card->hw.dpmbase + PPP514_FLG_OFFS);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ flags = card->flags;
+
+ /* Read firmware version. Note that when adapter initializes, it
+ * clears the mailbox, so it may appear that the first command was
+ * executed successfully when in fact it was merely erased. To work
+ * around this, we execute the first command twice.
+ */
+ if (ppp_read_version(card, NULL) || ppp_read_version(card, u.str))
+ return -EIO;
+
+ printk(KERN_INFO "%s: running PPP firmware v%s\n",card->devname, u.str);
+ /* Adjust configuration and set defaults */
+ card->wandev.mtu = (conf->mtu) ?
+ min_t(unsigned int, conf->mtu, PPP_MAX_MTU) : PPP_DFLT_MTU;
+
+ card->wandev.bps = conf->bps;
+ card->wandev.interface = conf->interface;
+ card->wandev.clocking = conf->clocking;
+ card->wandev.station = conf->station;
+ card->isr = &wpp_isr;
+ card->poll = NULL;
+ card->exec = &wpp_exec;
+ card->wandev.update = &update;
+ card->wandev.new_if = &new_if;
+ card->wandev.del_if = &del_if;
+ card->wandev.udp_port = conf->udp_port;
+ card->wandev.ttl = conf->ttl;
+ card->wandev.state = WAN_DISCONNECTED;
+ card->disable_comm = &disable_comm;
+ card->irq_dis_if_send_count = 0;
+ card->irq_dis_poll_count = 0;
+ card->u.p.authenticator = conf->u.ppp.authenticator;
+ card->u.p.ip_mode = conf->u.ppp.ip_mode ?
+ conf->u.ppp.ip_mode : WANOPT_PPP_STATIC;
+ card->TracingEnabled = 0;
+ Read_connection_info = 1;
+
+ /* initialize global statistics */
+ init_global_statistics( card );
+
+
+
+ if (!card->configured){
+ int err;
+
+ Intr_test_counter = 0;
+ err = intr_test(card);
+
+ if(err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) {
+ printk("%s: Interrupt Test Failed, Counter: %i\n",
+ card->devname, Intr_test_counter);
+ printk( "%s: Please choose another interrupt\n",card->devname);
+ return -EIO;
+ }
+
+ printk(KERN_INFO "%s: Interrupt Test Passed, Counter: %i\n",
+ card->devname, Intr_test_counter);
+ card->configured = 1;
+ }
+
+ ppp_set_intr_mode(card, PPP_INTR_TIMER);
+
+ /* Turn off the transmit and timer interrupt */
+ flags->imask &= ~PPP_INTR_TIMER;
+
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics.
+ */
+static int update(struct wan_device *wandev)
+{
+ sdla_t* card = wandev->private;
+ struct net_device* dev;
+ volatile ppp_private_area_t *ppp_priv_area;
+ ppp_flags_t *flags = card->flags;
+ unsigned long timeout;
+
+ /* sanity checks */
+ if ((wandev == NULL) || (wandev->private == NULL))
+ return -EFAULT;
+
+ if (wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ /* Shutdown bug fix. This function can be
+ * called with NULL dev pointer during
+ * shutdown
+ */
+ if ((dev=card->wandev.dev) == NULL){
+ return -ENODEV;
+ }
+
+ if ((ppp_priv_area=dev->priv) == NULL){
+ return -ENODEV;
+ }
+
+ ppp_priv_area->update_comms_stats = 2;
+ ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_UPDATE;
+ flags->imask |= PPP_INTR_TIMER;
+
+ /* wait a maximum of 1 second for the statistics to be updated */
+ timeout = jiffies;
+ for(;;) {
+ if(ppp_priv_area->update_comms_stats == 0){
+ break;
+ }
+ if ((jiffies - timeout) > (1 * HZ)){
+ ppp_priv_area->update_comms_stats = 0;
+ ppp_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_UPDATE;
+ return -EAGAIN;
+ }
+ }
+
+ return 0;
+}
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return: 0 o.k.
+ * < 0 failure (channel will not be created)
+ */
+static int new_if(struct wan_device *wandev, struct net_device *dev,
+ wanif_conf_t *conf)
+{
+ sdla_t *card = wandev->private;
+ ppp_private_area_t *ppp_priv_area;
+
+ if (wandev->ndev)
+ return -EEXIST;
+
+
+ printk(KERN_INFO "%s: Configuring Interface: %s\n",
+ card->devname, conf->name);
+
+ if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+
+ printk(KERN_INFO "%s: Invalid interface name!\n",
+ card->devname);
+ return -EINVAL;
+
+ }
+
+ /* allocate and initialize private data */
+ ppp_priv_area = kmalloc(sizeof(ppp_private_area_t), GFP_KERNEL);
+
+ if( ppp_priv_area == NULL )
+ return -ENOMEM;
+
+ memset(ppp_priv_area, 0, sizeof(ppp_private_area_t));
+
+ ppp_priv_area->card = card;
+
+ /* initialize data */
+ strcpy(card->u.p.if_name, conf->name);
+
+ /* initialize data in ppp_private_area structure */
+
+ init_ppp_priv_struct( ppp_priv_area );
+
+ ppp_priv_area->mc = conf->mc;
+ ppp_priv_area->pap = conf->pap;
+ ppp_priv_area->chap = conf->chap;
+
+ /* Option to bring down the interface when
+ * the link goes down */
+ if (conf->if_down){
+ set_bit(DYN_OPT_ON,&ppp_priv_area->interface_down);
+ printk("%s: Dynamic interface configuration enabled\n",
+ card->devname);
+ }
+
+ /* If no user ids are specified */
+ if(!strlen(conf->userid) && (ppp_priv_area->pap||ppp_priv_area->chap)){
+ kfree(ppp_priv_area);
+ return -EINVAL;
+ }
+
+ /* If no passwords are specified */
+ if(!strlen(conf->passwd) && (ppp_priv_area->pap||ppp_priv_area->chap)){
+ kfree(ppp_priv_area);
+ return -EINVAL;
+ }
+
+ if(strlen(conf->sysname) > 31){
+ kfree(ppp_priv_area);
+ return -EINVAL;
+ }
+
+ /* If no system name is specified */
+ if(!strlen(conf->sysname) && (card->u.p.authenticator)){
+ kfree(ppp_priv_area);
+ return -EINVAL;
+ }
+
+ /* copy the data into the ppp private structure */
+ memcpy(ppp_priv_area->userid, conf->userid, strlen(conf->userid));
+ memcpy(ppp_priv_area->passwd, conf->passwd, strlen(conf->passwd));
+ memcpy(ppp_priv_area->sysname, conf->sysname, strlen(conf->sysname));
+
+
+ ppp_priv_area->enable_IPX = conf->enable_IPX;
+ if (conf->network_number){
+ ppp_priv_area->network_number = conf->network_number;
+ }else{
+ ppp_priv_area->network_number = 0xDEADBEEF;
+ }
+
+ /* Tells us that if this interface is a
+ * gateway or not */
+ if ((ppp_priv_area->gateway = conf->gateway) == WANOPT_YES){
+ printk(KERN_INFO "%s: Interface %s is set as a gateway.\n",
+ card->devname,card->u.p.if_name);
+ }
+
+ /* prepare network device data space for registration */
+ strcpy(dev->name,card->u.p.if_name);
+
+ dev->init = &if_init;
+ dev->priv = ppp_priv_area;
+ dev->mtu = min_t(unsigned int, dev->mtu, card->wandev.mtu);
+
+ /* Initialize the polling work routine */
+ INIT_WORK(&ppp_priv_area->poll_work, (void*)(void*)ppp_poll, dev);
+
+ /* Initialize the polling delay timer */
+ init_timer(&ppp_priv_area->poll_delay_timer);
+ ppp_priv_area->poll_delay_timer.data = (unsigned long)dev;
+ ppp_priv_area->poll_delay_timer.function = ppp_poll_delay;
+
+
+ /* Since we start with dummy IP addresses we can say
+ * that route exists */
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+/*============================================================================
+ * Delete logical channel.
+ */
+static int del_if(struct wan_device *wandev, struct net_device *dev)
+{
+ return 0;
+}
+
+static void disable_comm (sdla_t *card)
+{
+ ppp_comm_disable_shutdown(card);
+ return;
+}
+
+/****** WANPIPE-specific entry points ***************************************/
+
+/*============================================================================
+ * Execute adapter interface command.
+ */
+
+//FIXME: Why do we need this ????
+static int wpp_exec(struct sdla *card, void *u_cmd, void *u_data)
+{
+ ppp_mbox_t *mbox = card->mbox;
+ int len;
+
+ if (copy_from_user((void*)&mbox->cmd, u_cmd, sizeof(ppp_cmd_t)))
+ return -EFAULT;
+
+ len = mbox->cmd.length;
+
+ if (len) {
+
+ if( copy_from_user((void*)&mbox->data, u_data, len))
+ return -EFAULT;
+
+ }
+
+ /* execute command */
+ if (!sdla_exec(mbox))
+ return -EIO;
+
+ /* return result */
+ if( copy_to_user(u_cmd, (void*)&mbox->cmd, sizeof(ppp_cmd_t)))
+ return -EFAULT;
+ len = mbox->cmd.length;
+
+ if (len && u_data && copy_to_user(u_data, (void*)&mbox->data, len))
+ return -EFAULT;
+
+ return 0;
+}
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration. Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device *dev)
+{
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ sdla_t *card = ppp_priv_area->card;
+ struct wan_device *wandev = &card->wandev;
+
+ /* Initialize device driver entry points */
+ dev->open = &if_open;
+ dev->stop = &if_close;
+ dev->hard_header = &if_header;
+ dev->rebuild_header = &if_rebuild_hdr;
+ dev->hard_start_xmit = &if_send;
+ dev->get_stats = &if_stats;
+ dev->tx_timeout = &if_tx_timeout;
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+ /* Initialize media-specific parameters */
+ dev->type = ARPHRD_PPP; /* ARP h/w type */
+ dev->flags |= IFF_POINTOPOINT;
+ dev->flags |= IFF_NOARP;
+
+ /* Enable Mulitcasting if specified by user*/
+ if (ppp_priv_area->mc == WANOPT_YES){
+ dev->flags |= IFF_MULTICAST;
+ }
+
+ dev->mtu = wandev->mtu;
+ dev->hard_header_len = PPP_HDR_LEN; /* media header length */
+
+ /* Initialize hardware parameters (just for reference) */
+ dev->irq = wandev->irq;
+ dev->dma = wandev->dma;
+ dev->base_addr = wandev->ioport;
+ dev->mem_start = wandev->maddr;
+ dev->mem_end = wandev->maddr + wandev->msize - 1;
+
+ /* Set transmit buffer queue length */
+ dev->tx_queue_len = 100;
+ SET_MODULE_OWNER(dev);
+
+ return 0;
+}
+
+/*============================================================================
+ * Open network interface.
+ * o enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device *dev)
+{
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ sdla_t *card = ppp_priv_area->card;
+ struct timeval tv;
+ //unsigned long smp_flags;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ wanpipe_open(card);
+
+ netif_start_queue(dev);
+
+ do_gettimeofday( &tv );
+ ppp_priv_area->router_start_time = tv.tv_sec;
+
+ /* We cannot configure the card here because we don't
+ * have access to the interface IP addresses.
+ * Once the interface initilization is complete, we will be
+ * able to access the IP addresses. Therefore,
+ * configure the ppp link in the poll routine */
+ set_bit(0,&ppp_priv_area->config_ppp);
+ ppp_priv_area->config_wait_timeout=jiffies;
+
+ /* Start the PPP configuration after 1sec delay.
+ * This will give the interface initilization time
+ * to finish its configuration */
+ mod_timer(&ppp_priv_area->poll_delay_timer, jiffies + HZ);
+ return 0;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last open, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device *dev)
+{
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ sdla_t *card = ppp_priv_area->card;
+
+ netif_stop_queue(dev);
+ wanpipe_close(card);
+
+ del_timer (&ppp_priv_area->poll_delay_timer);
+ return 0;
+}
+
+/*============================================================================
+ * Build media header.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol' field of
+ * the socket buffer, so that we don't forget it. If packet type is not
+ * supported, set skb->protocol to 0 and discard packet later.
+ *
+ * Return: media header length.
+ */
+static int if_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr, unsigned len)
+{
+ switch (type)
+ {
+ case ETH_P_IP:
+ case ETH_P_IPX:
+ skb->protocol = htons(type);
+ break;
+
+ default:
+ skb->protocol = 0;
+ }
+
+ return PPP_HDR_LEN;
+}
+
+/*============================================================================
+ * Re-build media header.
+ *
+ * Return: 1 physical address resolved.
+ * 0 physical address not resolved
+ */
+static int if_rebuild_hdr (struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ sdla_t *card = ppp_priv_area->card;
+
+ printk(KERN_INFO "%s: rebuild_header() called for interface %s!\n",
+ card->devname, dev->name);
+ return 1;
+}
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+ ppp_private_area_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ /* If our device stays busy for at least 5 seconds then we will
+ * kick start the device by making dev->tbusy = 0. We expect
+ * that our device never stays busy more than 5 seconds. So this
+ * is only used as a last resort.
+ */
+
+ ++ chan->if_send_stat.if_send_tbusy;
+ ++card->wandev.stats.collisions;
+
+ printk (KERN_INFO "%s: Transmit timed out on %s\n", card->devname,dev->name);
+ ++chan->if_send_stat.if_send_tbusy_timeout;
+ netif_wake_queue (dev);
+}
+
+
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ * transmit from overlapping.
+ * o check link state. If link is not up, then drop the packet.
+ * o execute adapter send command.
+ * o free socket buffer
+ *
+ * Return: 0 complete (socket buffer must be freed)
+ * non-0 packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ * bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ * protocol stack and can be used for flow control with protocol layer.
+ */
+static int if_send (struct sk_buff *skb, struct net_device *dev)
+{
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ sdla_t *card = ppp_priv_area->card;
+ unsigned char *sendpacket;
+ unsigned long smp_flags;
+ ppp_flags_t *flags = card->flags;
+ int udp_type;
+ int err=0;
+
+ ++ppp_priv_area->if_send_stat.if_send_entry;
+
+ netif_stop_queue(dev);
+
+ if (skb == NULL) {
+
+ /* If we get here, some higher layer thinks we've missed an
+ * tx-done interrupt.
+ */
+ printk(KERN_INFO "%s: interface %s got kicked!\n",
+ card->devname, dev->name);
+
+ ++ppp_priv_area->if_send_stat.if_send_skb_null;
+
+ netif_wake_queue(dev);
+ return 0;
+ }
+
+ sendpacket = skb->data;
+
+ udp_type = udp_pkt_type( skb, card );
+
+
+ if (udp_type == UDP_PTPIPE_TYPE){
+ if(store_udp_mgmt_pkt(UDP_PKT_FRM_STACK, card, skb, dev,
+ ppp_priv_area)){
+ flags->imask |= PPP_INTR_TIMER;
+ }
+ ++ppp_priv_area->if_send_stat.if_send_PIPE_request;
+ netif_start_queue(dev);
+ return 0;
+ }
+
+ /* Check for broadcast and multicast addresses
+ * If found, drop (deallocate) a packet and return.
+ */
+ if(chk_bcast_mcast_addr(card, dev, skb)){
+ ++card->wandev.stats.tx_dropped;
+ dev_kfree_skb_any(skb);
+ netif_start_queue(dev);
+ return 0;
+ }
+
+
+ if(card->hw.type != SDLA_S514){
+ s508_lock(card,&smp_flags);
+ }
+
+ if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+
+ printk(KERN_INFO "%s: Critical in if_send: %lx\n",
+ card->wandev.name,card->wandev.critical);
+
+ ++card->wandev.stats.tx_dropped;
+ ++ppp_priv_area->if_send_stat.if_send_critical_non_ISR;
+ netif_start_queue(dev);
+ goto if_send_exit_crit;
+ }
+
+ if (card->wandev.state != WAN_CONNECTED) {
+
+ ++ppp_priv_area->if_send_stat.if_send_wan_disconnected;
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+
+ } else if (!skb->protocol) {
+ ++ppp_priv_area->if_send_stat.if_send_protocol_error;
+ ++card->wandev.stats.tx_errors;
+ netif_start_queue(dev);
+
+ } else {
+
+ /*If it's IPX change the network numbers to 0 if they're ours.*/
+ if( skb->protocol == htons(ETH_P_IPX) ) {
+ if(ppp_priv_area->enable_IPX) {
+ switch_net_numbers( skb->data,
+ ppp_priv_area->network_number, 0);
+ } else {
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+ goto if_send_exit_crit;
+ }
+ }
+
+ if (ppp_send(card, skb->data, skb->len, skb->protocol)) {
+ netif_stop_queue(dev);
+ ++ppp_priv_area->if_send_stat.if_send_adptr_bfrs_full;
+ ++ppp_priv_area->if_send_stat.if_send_tx_int_enabled;
+ } else {
+ ++ppp_priv_area->if_send_stat.if_send_bfr_passed_to_adptr;
+ ++card->wandev.stats.tx_packets;
+ card->wandev.stats.tx_bytes += skb->len;
+ netif_start_queue(dev);
+ dev->trans_start = jiffies;
+ }
+ }
+
+if_send_exit_crit:
+
+ if (!(err=netif_queue_stopped(dev))){
+ dev_kfree_skb_any(skb);
+ }else{
+ ppp_priv_area->tick_counter = jiffies;
+ flags->imask |= PPP_INTR_TXRDY; /* unmask Tx interrupts */
+ }
+
+ clear_bit(SEND_CRIT,&card->wandev.critical);
+ if(card->hw.type != SDLA_S514){
+ s508_unlock(card,&smp_flags);
+ }
+
+ return err;
+}
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, struct net_device* dev,
+ ppp_private_area_t* ppp_priv_area )
+{
+ int udp_pkt_stored = 0;
+
+ if(!ppp_priv_area->udp_pkt_lgth && (skb->len<=MAX_LGTH_UDP_MGNT_PKT)){
+ ppp_priv_area->udp_pkt_lgth = skb->len;
+ ppp_priv_area->udp_pkt_src = udp_pkt_src;
+ memcpy(ppp_priv_area->udp_pkt_data, skb->data, skb->len);
+ ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_UDP;
+ ppp_priv_area->protocol = skb->protocol;
+ udp_pkt_stored = 1;
+ }else{
+ if (skb->len > MAX_LGTH_UDP_MGNT_PKT){
+ printk(KERN_INFO "%s: PIPEMON UDP request too long : %i\n",
+ card->devname, skb->len);
+ }else{
+ printk(KERN_INFO "%s: PIPEMON UPD request already pending\n",
+ card->devname);
+ }
+ ppp_priv_area->udp_pkt_lgth = 0;
+ }
+
+ if(udp_pkt_src == UDP_PKT_FRM_STACK){
+ dev_kfree_skb_any(skb);
+ }else{
+ dev_kfree_skb_any(skb);
+ }
+
+ return(udp_pkt_stored);
+}
+
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return length of reply.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+ unsigned short len, udp_length, temp, ip_length;
+ unsigned long ip_temp;
+ int even_bound = 0;
+ ppp_udp_pkt_t *p_udp_pkt = (ppp_udp_pkt_t *)data;
+
+ /* Set length of packet */
+ len = sizeof(ip_pkt_t)+
+ sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ mbox_len;
+
+ /* fill in UDP reply */
+ p_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+
+ /* fill in UDP length */
+ udp_length = sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ mbox_len;
+
+
+ /* put it on an even boundary */
+ if ( udp_length & 0x0001 ) {
+ udp_length += 1;
+ len += 1;
+ even_bound=1;
+ }
+
+ temp = (udp_length<<8)|(udp_length>>8);
+ p_udp_pkt->udp_pkt.udp_length = temp;
+
+
+ /* swap UDP ports */
+ temp = p_udp_pkt->udp_pkt.udp_src_port;
+ p_udp_pkt->udp_pkt.udp_src_port =
+ p_udp_pkt->udp_pkt.udp_dst_port;
+ p_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+
+ /* add UDP pseudo header */
+ temp = 0x1100;
+ *((unsigned short *)(p_udp_pkt->data+mbox_len+even_bound)) = temp;
+ temp = (udp_length<<8)|(udp_length>>8);
+ *((unsigned short *)(p_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+ /* calculate UDP checksum */
+ p_udp_pkt->udp_pkt.udp_checksum = 0;
+ p_udp_pkt->udp_pkt.udp_checksum =
+ calc_checksum(&data[UDP_OFFSET],udp_length+UDP_OFFSET);
+
+ /* fill in IP length */
+ ip_length = udp_length + sizeof(ip_pkt_t);
+ temp = (ip_length<<8)|(ip_length>>8);
+ p_udp_pkt->ip_pkt.total_length = temp;
+
+ /* swap IP addresses */
+ ip_temp = p_udp_pkt->ip_pkt.ip_src_address;
+ p_udp_pkt->ip_pkt.ip_src_address = p_udp_pkt->ip_pkt.ip_dst_address;
+ p_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+ /* fill in IP checksum */
+ p_udp_pkt->ip_pkt.hdr_checksum = 0;
+ p_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data,sizeof(ip_pkt_t));
+
+ return len;
+
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+ unsigned short temp;
+ unsigned long sum=0;
+ int i;
+
+ for( i = 0; i <len; i+=2 ) {
+ memcpy(&temp,&data[i],2);
+ sum += (unsigned long)temp;
+ }
+
+ while (sum >> 16 ) {
+ sum = (sum & 0xffffUL) + (sum >> 16);
+ }
+
+ temp = (unsigned short)sum;
+ temp = ~temp;
+
+ if( temp == 0 )
+ temp = 0xffff;
+
+ return temp;
+}
+
+/*
+ If incoming is 0 (outgoing)- if the net numbers is ours make it 0
+ if incoming is 1 - if the net number is 0 make it ours
+
+*/
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, unsigned char incoming)
+{
+ unsigned long pnetwork_number;
+
+ pnetwork_number = (unsigned long)((sendpacket[6] << 24) +
+ (sendpacket[7] << 16) + (sendpacket[8] << 8) +
+ sendpacket[9]);
+
+ if (!incoming) {
+ //If the destination network number is ours, make it 0
+ if( pnetwork_number == network_number) {
+ sendpacket[6] = sendpacket[7] = sendpacket[8] =
+ sendpacket[9] = 0x00;
+ }
+ } else {
+ //If the incoming network is 0, make it ours
+ if( pnetwork_number == 0) {
+ sendpacket[6] = (unsigned char)(network_number >> 24);
+ sendpacket[7] = (unsigned char)((network_number &
+ 0x00FF0000) >> 16);
+ sendpacket[8] = (unsigned char)((network_number &
+ 0x0000FF00) >> 8);
+ sendpacket[9] = (unsigned char)(network_number &
+ 0x000000FF);
+ }
+ }
+
+
+ pnetwork_number = (unsigned long)((sendpacket[18] << 24) +
+ (sendpacket[19] << 16) + (sendpacket[20] << 8) +
+ sendpacket[21]);
+
+ if( !incoming ) {
+ //If the source network is ours, make it 0
+ if( pnetwork_number == network_number) {
+ sendpacket[18] = sendpacket[19] = sendpacket[20] =
+ sendpacket[21] = 0x00;
+ }
+ } else {
+ //If the source network is 0, make it ours
+ if( pnetwork_number == 0 ) {
+ sendpacket[18] = (unsigned char)(network_number >> 24);
+ sendpacket[19] = (unsigned char)((network_number &
+ 0x00FF0000) >> 16);
+ sendpacket[20] = (unsigned char)((network_number &
+ 0x0000FF00) >> 8);
+ sendpacket[21] = (unsigned char)(network_number &
+ 0x000000FF);
+ }
+ }
+} /* switch_net_numbers */
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct net_device_stats.
+ */
+static struct net_device_stats *if_stats(struct net_device *dev)
+{
+
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ sdla_t* card;
+
+ if( ppp_priv_area == NULL )
+ return NULL;
+
+ card = ppp_priv_area->card;
+ return &card->wandev.stats;
+}
+
+/****** PPP Firmware Interface Functions ************************************/
+
+/*============================================================================
+ * Read firmware code version.
+ * Put code version as ASCII string in str.
+ */
+static int ppp_read_version(sdla_t *card, char *str)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.command = PPP_READ_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK)
+
+ ppp_error(card, err, mb);
+
+ else if (str) {
+
+ int len = mb->cmd.length;
+
+ memcpy(str, mb->data, len);
+ str[len] = '\0';
+
+ }
+
+ return err;
+}
+/*===========================================================================
+ * Set Out-Bound Authentication.
+*/
+static int ppp_set_outbnd_auth (sdla_t *card, ppp_private_area_t *ppp_priv_area)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ memset(&mb->data, 0, (strlen(ppp_priv_area->userid) +
+ strlen(ppp_priv_area->passwd) + 2 ) );
+ memcpy(mb->data, ppp_priv_area->userid, strlen(ppp_priv_area->userid));
+ memcpy((mb->data + strlen(ppp_priv_area->userid) + 1),
+ ppp_priv_area->passwd, strlen(ppp_priv_area->passwd));
+
+ mb->cmd.length = strlen(ppp_priv_area->userid) +
+ strlen(ppp_priv_area->passwd) + 2 ;
+
+ mb->cmd.command = PPP_SET_OUTBOUND_AUTH;
+
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+
+ return err;
+}
+
+/*===========================================================================
+ * Set In-Bound Authentication.
+*/
+static int ppp_set_inbnd_auth (sdla_t *card, ppp_private_area_t *ppp_priv_area)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err, i;
+ char* user_tokens[32];
+ char* pass_tokens[32];
+ int userids, passwds;
+ int add_ptr;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ memset(&mb->data, 0, 1008);
+ memcpy(mb->data, ppp_priv_area->sysname,
+ strlen(ppp_priv_area->sysname));
+
+ /* Parse the userid string and the password string and build a string
+ to copy it to the data area of the command structure. The string
+ will look like "SYS_NAME<NULL>USER1<NULL>PASS1<NULL>USER2<NULL>PASS2
+ ....<NULL> "
+ */
+ userids = tokenize( ppp_priv_area->userid, user_tokens);
+ passwds = tokenize( ppp_priv_area->passwd, pass_tokens);
+
+ if (userids != passwds){
+ printk(KERN_INFO "%s: Number of passwords does not equal the number of user ids\n", card->devname);
+ return 1;
+ }
+
+ add_ptr = strlen(ppp_priv_area->sysname) + 1;
+ for (i=0; i<userids; i++){
+ memcpy((mb->data + add_ptr), user_tokens[i],
+ strlen(user_tokens[i]));
+ memcpy((mb->data + add_ptr + strlen(user_tokens[i]) + 1),
+ pass_tokens[i], strlen(pass_tokens[i]));
+ add_ptr = add_ptr + strlen(user_tokens[i]) + 1 +
+ strlen(pass_tokens[i]) + 1;
+ }
+
+ mb->cmd.length = add_ptr + 1;
+ mb->cmd.command = PPP_SET_INBOUND_AUTH;
+
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+
+ return err;
+}
+
+
+/*============================================================================
+ * Tokenize string.
+ * Parse a string of the following syntax:
+ * <arg1>,<arg2>,...
+ * and fill array of tokens with pointers to string elements.
+ *
+ */
+static int tokenize (char *str, char **tokens)
+{
+ int cnt = 0;
+
+ tokens[0] = strsep(&str, "/");
+ while (tokens[cnt] && (cnt < 32 - 1))
+ {
+ tokens[cnt] = strstrip(tokens[cnt], " \t");
+ tokens[++cnt] = strsep(&str, "/");
+ }
+ return cnt;
+}
+
+/*============================================================================
+ * Strip leading and trailing spaces off the string str.
+ */
+static char* strstrip (char *str, char* s)
+{
+ char *eos = str + strlen(str); /* -> end of string */
+
+ while (*str && strchr(s, *str))
+ ++str /* strip leading spaces */
+ ;
+ while ((eos > str) && strchr(s, *(eos - 1)))
+ --eos /* strip trailing spaces */
+ ;
+ *eos = '\0';
+ return str;
+}
+/*============================================================================
+ * Configure PPP firmware.
+ */
+static int ppp_configure(sdla_t *card, void *data)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int data_len = sizeof(ppp508_conf_t);
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ memcpy(mb->data, data, data_len);
+ mb->cmd.length = data_len;
+ mb->cmd.command = PPP_SET_CONFIG;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+
+ return err;
+}
+
+/*============================================================================
+ * Set interrupt mode.
+ */
+static int ppp_set_intr_mode(sdla_t *card, unsigned char mode)
+{
+ ppp_mbox_t *mb = card->mbox;
+ ppp_intr_info_t *ppp_intr_data = (ppp_intr_info_t *) &mb->data[0];
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ ppp_intr_data->i_enable = mode;
+
+ ppp_intr_data->irq = card->hw.irq;
+ mb->cmd.length = 2;
+
+ /* If timer has been enabled, set the timer delay to 1sec */
+ if (mode & 0x80){
+ ppp_intr_data->timer_len = 250; //5;//100; //250;
+ mb->cmd.length = 4;
+ }
+
+ mb->cmd.command = PPP_SET_INTR_FLAGS;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+
+
+ return err;
+}
+
+/*============================================================================
+ * Enable communications.
+ */
+static int ppp_comm_enable(sdla_t *card)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.command = PPP_COMM_ENABLE;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+ else
+ card->u.p.comm_enabled = 1;
+
+ return err;
+}
+
+/*============================================================================
+ * Disable communications.
+ */
+static int ppp_comm_disable(sdla_t *card)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.command = PPP_COMM_DISABLE;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+ else
+ card->u.p.comm_enabled = 0;
+
+ return err;
+}
+
+static int ppp_comm_disable_shutdown(sdla_t *card)
+{
+ ppp_mbox_t *mb = card->mbox;
+ ppp_intr_info_t *ppp_intr_data;
+ int err;
+
+ if (!mb){
+ return 1;
+ }
+
+ ppp_intr_data = (ppp_intr_info_t *) &mb->data[0];
+
+ /* Disable all interrupts */
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ ppp_intr_data->i_enable = 0;
+
+ ppp_intr_data->irq = card->hw.irq;
+ mb->cmd.length = 2;
+
+ mb->cmd.command = PPP_SET_INTR_FLAGS;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ /* Disable communicatinons */
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.command = PPP_COMM_DISABLE;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ card->u.p.comm_enabled = 0;
+
+ return 0;
+}
+
+
+
+/*============================================================================
+ * Get communications error statistics.
+ */
+static int ppp_get_err_stats(sdla_t *card)
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.command = PPP_READ_ERROR_STATS;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err == CMD_OK) {
+
+ ppp_err_stats_t* stats = (void*)mb->data;
+ card->wandev.stats.rx_over_errors = stats->rx_overrun;
+ card->wandev.stats.rx_crc_errors = stats->rx_bad_crc;
+ card->wandev.stats.rx_missed_errors = stats->rx_abort;
+ card->wandev.stats.rx_length_errors = stats->rx_lost;
+ card->wandev.stats.tx_aborted_errors = stats->tx_abort;
+
+ } else
+ ppp_error(card, err, mb);
+
+ return err;
+}
+
+/*============================================================================
+ * Send packet.
+ * Return: 0 - o.k.
+ * 1 - no transmit buffers available
+ */
+static int ppp_send (sdla_t *card, void *data, unsigned len, unsigned proto)
+{
+ ppp_buf_ctl_t *txbuf = card->u.p.txbuf;
+
+ if (txbuf->flag)
+ return 1;
+
+ sdla_poke(&card->hw, txbuf->buf.ptr, data, len);
+
+ txbuf->length = len; /* frame length */
+
+ if (proto == htons(ETH_P_IPX))
+ txbuf->proto = 0x01; /* protocol ID */
+ else
+ txbuf->proto = 0x00; /* protocol ID */
+
+ txbuf->flag = 1; /* start transmission */
+
+ /* Update transmit buffer control fields */
+ card->u.p.txbuf = ++txbuf;
+
+ if ((void*)txbuf > card->u.p.txbuf_last)
+ card->u.p.txbuf = card->u.p.txbuf_base;
+
+ return 0;
+}
+
+/****** Firmware Error Handler **********************************************/
+
+/*============================================================================
+ * Firmware error handler.
+ * This routine is called whenever firmware command returns non-zero
+ * return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int ppp_error(sdla_t *card, int err, ppp_mbox_t *mb)
+{
+ unsigned cmd = mb->cmd.command;
+
+ switch (err) {
+
+ case CMD_TIMEOUT:
+ printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+ card->devname, cmd);
+ break;
+
+ default:
+ printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n"
+ , card->devname, cmd, err);
+ }
+
+ return 0;
+}
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * PPP interrupt service routine.
+ */
+static void wpp_isr (sdla_t *card)
+{
+ ppp_flags_t *flags = card->flags;
+ char *ptr = &flags->iflag;
+ struct net_device *dev = card->wandev.dev;
+ int i;
+
+ card->in_isr = 1;
+ ++card->statistics.isr_entry;
+
+ if (!dev && flags->iflag != PPP_INTR_CMD){
+ card->in_isr = 0;
+ flags->iflag = 0;
+ return;
+ }
+
+ if (test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+ card->in_isr = 0;
+ flags->iflag = 0;
+ return;
+ }
+
+
+ if(card->hw.type != SDLA_S514){
+ if (test_bit(SEND_CRIT, (void*)&card->wandev.critical)) {
+ ++card->statistics.isr_already_critical;
+ printk (KERN_INFO "%s: Critical while in ISR!\n",
+ card->devname);
+ card->in_isr = 0;
+ flags->iflag = 0;
+ return;
+ }
+ }
+
+ switch (flags->iflag) {
+
+ case PPP_INTR_RXRDY: /* receive interrupt 0x01 (bit 0)*/
+ ++card->statistics.isr_rx;
+ rx_intr(card);
+ break;
+
+ case PPP_INTR_TXRDY: /* transmit interrupt 0x02 (bit 1)*/
+ ++card->statistics.isr_tx;
+ flags->imask &= ~PPP_INTR_TXRDY;
+ netif_wake_queue(dev);
+ break;
+
+ case PPP_INTR_CMD: /* interface command completed */
+ ++Intr_test_counter;
+ ++card->statistics.isr_intr_test;
+ break;
+
+ case PPP_INTR_MODEM: /* modem status change (DCD, CTS) 0x04 (bit 2)*/
+ case PPP_INTR_DISC: /* Data link disconnected 0x10 (bit 4)*/
+ case PPP_INTR_OPEN: /* Data link open 0x20 (bit 5)*/
+ case PPP_INTR_DROP_DTR: /* DTR drop timeout expired 0x40 bit 6 */
+ event_intr(card);
+ break;
+
+ case PPP_INTR_TIMER:
+ timer_intr(card);
+ break;
+
+ default: /* unexpected interrupt */
+ ++card->statistics.isr_spurious;
+ printk(KERN_INFO "%s: spurious interrupt 0x%02X!\n",
+ card->devname, flags->iflag);
+ printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+ for(i = 0; i < 8; i ++)
+ printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+ printk(KERN_INFO "\n");
+ }
+
+ card->in_isr = 0;
+ flags->iflag = 0;
+ return;
+}
+
+/*============================================================================
+ * Receive interrupt handler.
+ */
+static void rx_intr(sdla_t *card)
+{
+ ppp_buf_ctl_t *rxbuf = card->rxmb;
+ struct net_device *dev = card->wandev.dev;
+ ppp_private_area_t *ppp_priv_area;
+ struct sk_buff *skb;
+ unsigned len;
+ void *buf;
+ int i;
+ ppp_flags_t *flags = card->flags;
+ char *ptr = &flags->iflag;
+ int udp_type;
+
+
+ if (rxbuf->flag != 0x01) {
+
+ printk(KERN_INFO
+ "%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n",
+ card->devname, (unsigned)rxbuf, rxbuf->flag);
+
+ printk(KERN_INFO "%s: ID Bytes = ",card->devname);
+
+ for(i = 0; i < 8; i ++)
+ printk(KERN_INFO "0x%02X ", *(ptr + 0x28 + i));
+ printk(KERN_INFO "\n");
+
+ ++card->statistics.rx_intr_corrupt_rx_bfr;
+
+
+ /* Bug Fix: Mar 6 2000
+ * If we get a corrupted mailbox, it means that driver
+ * is out of sync with the firmware. There is no recovery.
+ * If we don't turn off all interrupts for this card
+ * the machine will crash.
+ */
+ printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+ printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+ ppp_set_intr_mode(card,0);
+ return;
+ }
+
+ if (dev && netif_running(dev) && dev->priv){
+
+ len = rxbuf->length;
+ ppp_priv_area = dev->priv;
+
+ /* Allocate socket buffer */
+ skb = dev_alloc_skb(len);
+
+ if (skb != NULL) {
+
+ /* Copy data to the socket buffer */
+ unsigned addr = rxbuf->buf.ptr;
+
+ if ((addr + len) > card->u.p.rx_top + 1) {
+
+ unsigned tmp = card->u.p.rx_top - addr + 1;
+ buf = skb_put(skb, tmp);
+ sdla_peek(&card->hw, addr, buf, tmp);
+ addr = card->u.p.rx_base;
+ len -= tmp;
+ }
+ buf = skb_put(skb, len);
+ sdla_peek(&card->hw, addr, buf, len);
+
+ /* Decapsulate packet */
+ switch (rxbuf->proto) {
+
+ case 0x00:
+ skb->protocol = htons(ETH_P_IP);
+ break;
+
+ case 0x01:
+ skb->protocol = htons(ETH_P_IPX);
+ break;
+ }
+
+ udp_type = udp_pkt_type( skb, card );
+
+ if (udp_type == UDP_PTPIPE_TYPE){
+
+ /* Handle a UDP Request in Timer Interrupt */
+ if(store_udp_mgmt_pkt(UDP_PKT_FRM_NETWORK, card, skb, dev,
+ ppp_priv_area)){
+ flags->imask |= PPP_INTR_TIMER;
+ }
+ ++ppp_priv_area->rx_intr_stat.rx_intr_PIPE_request;
+
+
+ } else if (handle_IPXWAN(skb->data,card->devname,
+ ppp_priv_area->enable_IPX,
+ ppp_priv_area->network_number,
+ skb->protocol)) {
+
+ /* Handle an IPXWAN packet */
+ if( ppp_priv_area->enable_IPX) {
+
+ /* Make sure we are not already sending */
+ if (!test_bit(SEND_CRIT, &card->wandev.critical)){
+ ppp_send(card, skb->data, skb->len, htons(ETH_P_IPX));
+ }
+ dev_kfree_skb_any(skb);
+
+ } else {
+ ++card->wandev.stats.rx_dropped;
+ }
+ } else {
+ /* Pass data up the protocol stack */
+ skb->dev = dev;
+ skb->mac.raw = skb->data;
+
+ ++card->wandev.stats.rx_packets;
+ card->wandev.stats.rx_bytes += skb->len;
+ ++ppp_priv_area->rx_intr_stat.rx_intr_bfr_passed_to_stack;
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ }
+
+ } else {
+
+ if (net_ratelimit()){
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ }
+ ++card->wandev.stats.rx_dropped;
+ ++ppp_priv_area->rx_intr_stat.rx_intr_no_socket;
+ }
+
+ } else {
+ ++card->statistics.rx_intr_dev_not_started;
+ }
+
+ /* Release buffer element and calculate a pointer to the next one */
+ rxbuf->flag = 0x00;
+ card->rxmb = ++rxbuf;
+ if ((void*)rxbuf > card->u.p.rxbuf_last)
+ card->rxmb = card->u.p.rxbuf_base;
+}
+
+
+void event_intr (sdla_t *card)
+{
+
+ struct net_device* dev = card->wandev.dev;
+ ppp_private_area_t* ppp_priv_area = dev->priv;
+ volatile ppp_flags_t *flags = card->flags;
+
+ switch (flags->iflag){
+
+ case PPP_INTR_MODEM: /* modem status change (DCD, CTS) 0x04 (bit 2)*/
+
+ if (net_ratelimit()){
+ printk (KERN_INFO "%s: Modem status: DCD=%s CTS=%s\n",
+ card->devname, DCD(flags->mstatus), CTS(flags->mstatus));
+ }
+ break;
+
+ case PPP_INTR_DISC: /* Data link disconnected 0x10 (bit 4)*/
+
+ NEX_PRINTK (KERN_INFO "Data link disconnected intr Cause %X\n",
+ flags->disc_cause);
+
+ if (flags->disc_cause &
+ (PPP_LOCAL_TERMINATION | PPP_DCD_CTS_DROP |
+ PPP_REMOTE_TERMINATION)) {
+
+ if (card->u.p.ip_mode == WANOPT_PPP_PEER) {
+ set_bit(0,&Read_connection_info);
+ }
+ wanpipe_set_state(card, WAN_DISCONNECTED);
+
+ show_disc_cause(card, flags->disc_cause);
+ ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_PPP_EVENT;
+ flags->imask |= PPP_INTR_TIMER;
+ trigger_ppp_poll(dev);
+ }
+ break;
+
+ case PPP_INTR_OPEN: /* Data link open 0x20 (bit 5)*/
+
+ NEX_PRINTK (KERN_INFO "%s: PPP Link Open, LCP=%s IP=%s\n",
+ card->devname,LCP(flags->lcp_state),
+ IP(flags->ip_state));
+
+ if (flags->lcp_state == 0x09 &&
+ (flags->ip_state == 0x09 || flags->ipx_state == 0x09)){
+
+ /* Initialize the polling timer and set the state
+ * to WAN_CONNNECTED */
+
+
+ /* BUG FIX: When the protocol restarts, during heavy
+ * traffic, board tx buffers and driver tx buffers
+ * can go out of sync. This checks the condition
+ * and if the tx buffers are out of sync, the
+ * protocols are restarted.
+ * I don't know why the board tx buffer is out
+ * of sync. It could be that a packets is tx
+ * while the link is down, but that is not
+ * possible. The other possiblility is that the
+ * firmware doesn't reinitialize properly.
+ * FIXME: A better fix should be found.
+ */
+ if (detect_and_fix_tx_bug(card)){
+
+ ppp_comm_disable(card);
+
+ wanpipe_set_state(card, WAN_DISCONNECTED);
+
+ ppp_priv_area->timer_int_enabled |=
+ TMR_INT_ENABLED_PPP_EVENT;
+ flags->imask |= PPP_INTR_TIMER;
+ break;
+ }
+
+ card->state_tick = jiffies;
+ wanpipe_set_state(card, WAN_CONNECTED);
+
+ NEX_PRINTK(KERN_INFO "CON: L Tx: %lx B Tx: %lx || L Rx %lx B Rx %lx\n",
+ (unsigned long)card->u.p.txbuf, *card->u.p.txbuf_next,
+ (unsigned long)card->rxmb, *card->u.p.rxbuf_next);
+
+ /* Tell timer interrupt that PPP event occurred */
+ ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_PPP_EVENT;
+ flags->imask |= PPP_INTR_TIMER;
+
+ /* If we are in PEER mode, we must first obtain the
+ * IP information and then go into the poll routine */
+ if (card->u.p.ip_mode != WANOPT_PPP_PEER){
+ trigger_ppp_poll(dev);
+ }
+ }
+ break;
+
+ case PPP_INTR_DROP_DTR: /* DTR drop timeout expired 0x40 bit 6 */
+
+ NEX_PRINTK(KERN_INFO "DTR Drop Timeout Interrrupt \n");
+
+ if (card->u.p.ip_mode == WANOPT_PPP_PEER) {
+ set_bit(0,&Read_connection_info);
+ }
+
+ wanpipe_set_state(card, WAN_DISCONNECTED);
+
+ show_disc_cause(card, flags->disc_cause);
+ ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_PPP_EVENT;
+ flags->imask |= PPP_INTR_TIMER;
+ trigger_ppp_poll(dev);
+ break;
+
+ default:
+ printk(KERN_INFO "%s: Error, Invalid PPP Event\n",card->devname);
+ }
+}
+
+
+
+/* TIMER INTERRUPT */
+
+void timer_intr (sdla_t *card)
+{
+
+ struct net_device* dev = card->wandev.dev;
+ ppp_private_area_t* ppp_priv_area = dev->priv;
+ ppp_flags_t *flags = card->flags;
+
+
+ if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_CONFIG){
+ if (!config_ppp(card)){
+ ppp_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_CONFIG;
+ }
+ }
+
+ /* Update statistics */
+ if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_UPDATE){
+ ppp_get_err_stats(card);
+ if(!(--ppp_priv_area->update_comms_stats)){
+ ppp_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_UPDATE;
+ }
+ }
+
+ /* PPIPEMON UDP request */
+
+ if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_UDP){
+ process_udp_mgmt_pkt(card,dev, ppp_priv_area);
+ ppp_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_UDP;
+ }
+
+ /* PPP Event */
+ if (ppp_priv_area->timer_int_enabled & TMR_INT_ENABLED_PPP_EVENT){
+
+ if (card->wandev.state == WAN_DISCONNECTED){
+ retrigger_comm(card);
+ }
+
+ /* If the state is CONNECTING, it means that communicatins were
+ * enabled. When the remote side enables its comminication we
+ * should get an interrupt PPP_INTR_OPEN, thus turn off polling
+ */
+
+ else if (card->wandev.state == WAN_CONNECTING){
+ /* Turn off the timer interrupt */
+ ppp_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_PPP_EVENT;
+ }
+
+ /* If state is connected and we are in PEER mode
+ * poll for an IP address which will be provided by remote end.
+ */
+ else if ((card->wandev.state == WAN_CONNECTED &&
+ card->u.p.ip_mode == WANOPT_PPP_PEER) &&
+ test_bit(0,&Read_connection_info)){
+
+ card->state_tick = jiffies;
+ if (read_connection_info (card)){
+ printk(KERN_INFO "%s: Failed to read PEER IP Addresses\n",
+ card->devname);
+ }else{
+ clear_bit(0,&Read_connection_info);
+ set_bit(1,&Read_connection_info);
+ trigger_ppp_poll(dev);
+ }
+ }else{
+ //FIXME Put the comment back int
+ ppp_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_PPP_EVENT;
+ }
+
+ }/* End of PPP_EVENT */
+
+
+ /* Only disable the timer interrupt if there are no udp, statistic */
+ /* updates or events pending */
+ if(!ppp_priv_area->timer_int_enabled) {
+ flags->imask &= ~PPP_INTR_TIMER;
+ }
+}
+
+
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname, unsigned char enable_IPX, unsigned long network_number, unsigned short proto)
+{
+ int i;
+
+ if( proto == htons(ETH_P_IPX) ) {
+ //It's an IPX packet
+ if(!enable_IPX) {
+ //Return 1 so we don't pass it up the stack.
+ return 1;
+ }
+ } else {
+ //It's not IPX so pass it up the stack.
+ return 0;
+ }
+
+ if( sendpacket[16] == 0x90 &&
+ sendpacket[17] == 0x04)
+ {
+ //It's IPXWAN
+
+ if( sendpacket[2] == 0x02 &&
+ sendpacket[34] == 0x00)
+ {
+ //It's a timer request packet
+ printk(KERN_INFO "%s: Received IPXWAN Timer Request packet\n",devname);
+
+ //Go through the routing options and answer no to every
+ //option except Unnumbered RIP/SAP
+ for(i = 41; sendpacket[i] == 0x00; i += 5)
+ {
+ //0x02 is the option for Unnumbered RIP/SAP
+ if( sendpacket[i + 4] != 0x02)
+ {
+ sendpacket[i + 1] = 0;
+ }
+ }
+
+ //Skip over the extended Node ID option
+ if( sendpacket[i] == 0x04 )
+ {
+ i += 8;
+ }
+
+ //We also want to turn off all header compression opt.
+ for(; sendpacket[i] == 0x80 ;)
+ {
+ sendpacket[i + 1] = 0;
+ i += (sendpacket[i + 2] << 8) + (sendpacket[i + 3]) + 4;
+ }
+
+ //Set the packet type to timer response
+ sendpacket[34] = 0x01;
+
+ printk(KERN_INFO "%s: Sending IPXWAN Timer Response\n",devname);
+ }
+ else if( sendpacket[34] == 0x02 )
+ {
+ //This is an information request packet
+ printk(KERN_INFO "%s: Received IPXWAN Information Request packet\n",devname);
+
+ //Set the packet type to information response
+ sendpacket[34] = 0x03;
+
+ //Set the router name
+ sendpacket[51] = 'P';
+ sendpacket[52] = 'T';
+ sendpacket[53] = 'P';
+ sendpacket[54] = 'I';
+ sendpacket[55] = 'P';
+ sendpacket[56] = 'E';
+ sendpacket[57] = '-';
+ sendpacket[58] = CVHexToAscii(network_number >> 28);
+ sendpacket[59] = CVHexToAscii((network_number & 0x0F000000)>> 24);
+ sendpacket[60] = CVHexToAscii((network_number & 0x00F00000)>> 20);
+ sendpacket[61] = CVHexToAscii((network_number & 0x000F0000)>> 16);
+ sendpacket[62] = CVHexToAscii((network_number & 0x0000F000)>> 12);
+ sendpacket[63] = CVHexToAscii((network_number & 0x00000F00)>> 8);
+ sendpacket[64] = CVHexToAscii((network_number & 0x000000F0)>> 4);
+ sendpacket[65] = CVHexToAscii(network_number & 0x0000000F);
+ for(i = 66; i < 99; i+= 1)
+ {
+ sendpacket[i] = 0;
+ }
+
+ printk(KERN_INFO "%s: Sending IPXWAN Information Response packet\n",devname);
+ }
+ else
+ {
+ printk(KERN_INFO "%s: Unknown IPXWAN packet!\n",devname);
+ return 0;
+ }
+
+ //Set the WNodeID to our network address
+ sendpacket[35] = (unsigned char)(network_number >> 24);
+ sendpacket[36] = (unsigned char)((network_number & 0x00FF0000) >> 16);
+ sendpacket[37] = (unsigned char)((network_number & 0x0000FF00) >> 8);
+ sendpacket[38] = (unsigned char)(network_number & 0x000000FF);
+
+ return 1;
+ } else {
+ //If we get here it's an IPX-data packet, so it'll get passed up the stack.
+
+ //switch the network numbers
+ switch_net_numbers(sendpacket, network_number, 1);
+ return 0;
+ }
+}
+
+/****** Background Polling Routines ****************************************/
+
+/* All polling functions are invoked by the TIMER interrupt in the wpp_isr
+ * routine.
+ */
+
+/*============================================================================
+ * Monitor active link phase.
+ */
+static void process_route (sdla_t *card)
+{
+ ppp_flags_t *flags = card->flags;
+ struct net_device *dev = card->wandev.dev;
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+
+ if ((card->u.p.ip_mode == WANOPT_PPP_PEER) &&
+ (flags->ip_state == 0x09)){
+
+ /* We get ip_local from the firmware in PEER mode.
+ * Therefore, if ip_local is 0, we failed to obtain
+ * the remote IP address. */
+ if (ppp_priv_area->ip_local == 0)
+ return;
+
+ printk(KERN_INFO "%s: IPCP State Opened.\n", card->devname);
+ if (read_info( card )) {
+ printk(KERN_INFO
+ "%s: An error occurred in IP assignment.\n",
+ card->devname);
+ } else {
+ struct in_device *in_dev = dev->ip_ptr;
+ if (in_dev != NULL ) {
+ struct in_ifaddr *ifa = in_dev->ifa_list;
+
+ printk(KERN_INFO "%s: Assigned Lcl. Addr: %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(ifa->ifa_local));
+ printk(KERN_INFO "%s: Assigned Rmt. Addr: %u.%u.%u.%u\n",
+ card->devname, NIPQUAD(ifa->ifa_address));
+ }else{
+ printk(KERN_INFO
+ "%s: Error: Failed to add a route for PPP interface %s\n",
+ card->devname,dev->name);
+ }
+ }
+ }
+}
+
+/*============================================================================
+ * Monitor physical link disconnected phase.
+ * o if interface is up and the hold-down timeout has expired, then retry
+ * connection.
+ */
+static void retrigger_comm(sdla_t *card)
+{
+ struct net_device *dev = card->wandev.dev;
+
+ if (dev && ((jiffies - card->state_tick) > HOLD_DOWN_TIME)) {
+
+ wanpipe_set_state(card, WAN_CONNECTING);
+
+ if(ppp_comm_enable(card) == CMD_OK){
+ init_ppp_tx_rx_buff( card );
+ }
+ }
+}
+
+/****** Miscellaneous Functions *********************************************/
+
+/*============================================================================
+ * Configure S508 adapter.
+ */
+static int config508(struct net_device *dev, sdla_t *card)
+{
+ ppp508_conf_t cfg;
+ struct in_device *in_dev = dev->ip_ptr;
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+
+ /* Prepare PPP configuration structure */
+ memset(&cfg, 0, sizeof(ppp508_conf_t));
+
+ if (card->wandev.clocking)
+ cfg.line_speed = card->wandev.bps;
+
+ if (card->wandev.interface == WANOPT_RS232)
+ cfg.conf_flags |= INTERFACE_LEVEL_RS232;
+
+
+ cfg.conf_flags |= DONT_TERMINATE_LNK_MAX_CONFIG; /*send Configure-Request packets forever*/
+ cfg.txbuf_percent = PERCENT_TX_BUFF; /* % of Tx bufs */
+ cfg.mtu_local = card->wandev.mtu;
+ cfg.mtu_remote = card->wandev.mtu; /* Default */
+ cfg.restart_tmr = TIME_BETWEEN_CONF_REQ; /* 30 = 3sec */
+ cfg.auth_rsrt_tmr = TIME_BETWEEN_PAP_CHAP_REQ; /* 30 = 3sec */
+ cfg.auth_wait_tmr = WAIT_PAP_CHAP_WITHOUT_REPLY; /* 300 = 30s */
+ cfg.mdm_fail_tmr = WAIT_AFTER_DCD_CTS_LOW; /* 5 = 0.5s */
+ cfg.dtr_drop_tmr = TIME_DCD_CTS_LOW_AFTER_LNK_DOWN; /* 10 = 1s */
+ cfg.connect_tmout = WAIT_DCD_HIGH_AFTER_ENABLE_COMM; /* 900 = 90s */
+ cfg.conf_retry = MAX_CONF_REQ_WITHOUT_REPLY; /* 10 = 1s */
+ cfg.term_retry = MAX_TERM_REQ_WITHOUT_REPLY; /* 2 times */
+ cfg.fail_retry = NUM_CONF_NAK_WITHOUT_REPLY; /* 5 times */
+ cfg.auth_retry = NUM_AUTH_REQ_WITHOUT_REPLY; /* 10 times */
+
+
+ if( !card->u.p.authenticator ) {
+ printk(KERN_INFO "%s: Device is not configured as an authenticator\n",
+ card->devname);
+ cfg.auth_options = NO_AUTHENTICATION;
+ }else{
+ printk(KERN_INFO "%s: Device is configured as an authenticator\n",
+ card->devname);
+ cfg.auth_options = INBOUND_AUTH;
+ }
+
+ if( ppp_priv_area->pap == WANOPT_YES){
+ cfg.auth_options |=PAP_AUTH;
+ printk(KERN_INFO "%s: Pap enabled\n", card->devname);
+ }
+ if( ppp_priv_area->chap == WANOPT_YES){
+ cfg.auth_options |= CHAP_AUTH;
+ printk(KERN_INFO "%s: Chap enabled\n", card->devname);
+ }
+
+
+ if (ppp_priv_area->enable_IPX == WANOPT_YES){
+ printk(KERN_INFO "%s: Enabling IPX Protocol\n",card->devname);
+ cfg.ipx_options = ENABLE_IPX | ROUTING_PROT_DEFAULT;
+ }else{
+ cfg.ipx_options = DISABLE_IPX;
+ }
+
+ switch (card->u.p.ip_mode) {
+
+ case WANOPT_PPP_STATIC:
+
+ printk(KERN_INFO "%s: PPP IP Mode: STATIC\n",card->devname);
+ cfg.ip_options = L_AND_R_IP_NO_ASSIG |
+ ENABLE_IP;
+ cfg.ip_local = in_dev->ifa_list->ifa_local;
+ cfg.ip_remote = in_dev->ifa_list->ifa_address;
+ /* Debugging code used to check that IP addresses
+ * obtained from the kernel are correct */
+
+ NEX_PRINTK(KERN_INFO "Local %u.%u.%u.%u Remote %u.%u.%u.%u Name %s\n",
+ NIPQUAD(ip_local),NIPQUAD(ip_remote), dev->name);
+ break;
+
+ case WANOPT_PPP_HOST:
+
+ printk(KERN_INFO "%s: PPP IP Mode: HOST\n",card->devname);
+ cfg.ip_options = L_IP_LOCAL_ASSIG |
+ R_IP_LOCAL_ASSIG |
+ ENABLE_IP;
+ cfg.ip_local = in_dev->ifa_list->ifa_local;
+ cfg.ip_remote = in_dev->ifa_list->ifa_address;
+ /* Debugging code used to check that IP addresses
+ * obtained from the kernel are correct */
+ NEX_PRINTK (KERN_INFO "Local %u.%u.%u.%u Remote %u.%u.%u.%u Name %s\n",
+ NIPQUAD(ip_local),NIPQUAD(ip_remote), dev->name);
+
+ break;
+
+ case WANOPT_PPP_PEER:
+
+ printk(KERN_INFO "%s: PPP IP Mode: PEER\n",card->devname);
+ cfg.ip_options = L_IP_REMOTE_ASSIG |
+ R_IP_REMOTE_ASSIG |
+ ENABLE_IP;
+ cfg.ip_local = 0x00;
+ cfg.ip_remote = 0x00;
+ break;
+
+ default:
+ printk(KERN_INFO "%s: ERROR: Unsupported PPP Mode Selected\n",
+ card->devname);
+ printk(KERN_INFO "%s: PPP IP Modes: STATIC, PEER or HOST\n",
+ card->devname);
+ return 1;
+ }
+
+ return ppp_configure(card, &cfg);
+}
+
+/*============================================================================
+ * Show disconnection cause.
+ */
+static void show_disc_cause(sdla_t *card, unsigned cause)
+{
+ if (cause & 0x0802)
+
+ printk(KERN_INFO "%s: link terminated by peer\n",
+ card->devname);
+
+ else if (cause & 0x0004)
+
+ printk(KERN_INFO "%s: link terminated by user\n",
+ card->devname);
+
+ else if (cause & 0x0008)
+
+ printk(KERN_INFO "%s: authentication failed\n", card->devname);
+
+ else if (cause & 0x0010)
+
+ printk(KERN_INFO
+ "%s: authentication protocol negotiation failed\n",
+ card->devname);
+
+ else if (cause & 0x0020)
+
+ printk(KERN_INFO
+ "%s: peer's request for authentication rejected\n",
+ card->devname);
+
+ else if (cause & 0x0040)
+
+ printk(KERN_INFO "%s: MRU option rejected by peer\n",
+ card->devname);
+
+ else if (cause & 0x0080)
+
+ printk(KERN_INFO "%s: peer's MRU was too small\n",
+ card->devname);
+
+ else if (cause & 0x0100)
+
+ printk(KERN_INFO "%s: failed to negotiate peer's LCP options\n",
+ card->devname);
+
+ else if (cause & 0x0200)
+
+ printk(KERN_INFO "%s: failed to negotiate peer's IPCP options\n"
+ , card->devname);
+
+ else if (cause & 0x0400)
+
+ printk(KERN_INFO
+ "%s: failed to negotiate peer's IPXCP options\n",
+ card->devname);
+}
+
+/*=============================================================================
+ * Process UDP call of type PTPIPEAB.
+ */
+static void process_udp_mgmt_pkt(sdla_t *card, struct net_device *dev,
+ ppp_private_area_t *ppp_priv_area )
+{
+ unsigned char buf2[5];
+ unsigned char *buf;
+ unsigned int frames, len;
+ struct sk_buff *new_skb;
+ unsigned short data_length, buffer_length, real_len;
+ unsigned long data_ptr;
+ int udp_mgmt_req_valid = 1;
+ ppp_mbox_t *mbox = card->mbox;
+ struct timeval tv;
+ int err;
+ ppp_udp_pkt_t *ppp_udp_pkt = (ppp_udp_pkt_t*)&ppp_priv_area->udp_pkt_data;
+
+ memcpy(&buf2, &card->wandev.udp_port, 2 );
+
+
+ if(ppp_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+ switch(ppp_udp_pkt->cblock.command) {
+
+ case PPIPE_GET_IBA_DATA:
+ case PPP_READ_CONFIG:
+ case PPP_GET_CONNECTION_INFO:
+ case PPIPE_ROUTER_UP_TIME:
+ case PPP_READ_STATISTICS:
+ case PPP_READ_ERROR_STATS:
+ case PPP_READ_PACKET_STATS:
+ case PPP_READ_LCP_STATS:
+ case PPP_READ_IPCP_STATS:
+ case PPP_READ_IPXCP_STATS:
+ case PPP_READ_PAP_STATS:
+ case PPP_READ_CHAP_STATS:
+ case PPP_READ_CODE_VERSION:
+ udp_mgmt_req_valid = 1;
+ break;
+
+ default:
+ udp_mgmt_req_valid = 0;
+ break;
+ }
+ }
+
+ if(!udp_mgmt_req_valid) {
+
+ /* set length to 0 */
+ ppp_udp_pkt->cblock.length = 0x00;
+
+ /* set return code */
+ ppp_udp_pkt->cblock.result = 0xCD;
+ ++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_direction_err;
+
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Warning, Illegal UDP command attempted from network: %x\n",
+ card->devname,ppp_udp_pkt->cblock.command);
+ }
+ } else {
+ /* Initialize the trace element */
+ trace_element_t trace_element;
+
+ switch (ppp_udp_pkt->cblock.command){
+
+ /* PPIPE_ENABLE_TRACING */
+ case PPIPE_ENABLE_TRACING:
+ if (!card->TracingEnabled) {
+
+ /* OPERATE_DATALINE_MONITOR */
+ mbox->cmd.command = PPP_DATALINE_MONITOR;
+ mbox->cmd.length = 0x01;
+ mbox->data[0] = ppp_udp_pkt->data[0];
+ err = sdla_exec(mbox) ?
+ mbox->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK) {
+
+ ppp_error(card, err, mbox);
+ card->TracingEnabled = 0;
+
+ /* set the return code */
+
+ ppp_udp_pkt->cblock.result = mbox->cmd.result;
+ mbox->cmd.length = 0;
+ break;
+ }
+
+ sdla_peek(&card->hw, 0xC000, &buf2, 2);
+
+ ppp_priv_area->curr_trace_addr = 0;
+ memcpy(&ppp_priv_area->curr_trace_addr, &buf2, 2);
+ ppp_priv_area->start_trace_addr =
+ ppp_priv_area->curr_trace_addr;
+ ppp_priv_area->end_trace_addr =
+ ppp_priv_area->start_trace_addr + END_OFFSET;
+
+ /* MAX_SEND_BUFFER_SIZE - 28 (IP header)
+ - 32 (ppipemon CBLOCK) */
+ available_buffer_space = MAX_LGTH_UDP_MGNT_PKT -
+ sizeof(ip_pkt_t)-
+ sizeof(udp_pkt_t)-
+ sizeof(wp_mgmt_t)-
+ sizeof(cblock_t);
+ }
+ ppp_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 0;
+ card->TracingEnabled = 1;
+ break;
+
+ /* PPIPE_DISABLE_TRACING */
+ case PPIPE_DISABLE_TRACING:
+
+ if(card->TracingEnabled) {
+
+ /* OPERATE_DATALINE_MONITOR */
+ mbox->cmd.command = 0x33;
+ mbox->cmd.length = 1;
+ mbox->data[0] = 0x00;
+ err = sdla_exec(mbox) ?
+ mbox->cmd.result : CMD_TIMEOUT;
+
+ }
+
+ /*set return code*/
+ ppp_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 0;
+ card->TracingEnabled = 0;
+ break;
+
+ /* PPIPE_GET_TRACE_INFO */
+ case PPIPE_GET_TRACE_INFO:
+
+ if(!card->TracingEnabled) {
+ /* set return code */
+ ppp_udp_pkt->cblock.result = 1;
+ mbox->cmd.length = 0;
+ }
+
+ buffer_length = 0;
+
+ /* frames < 62, where 62 is the number of trace
+ information elements. There is in total 496
+ bytes of space and each trace information
+ element is 8 bytes.
+ */
+ for ( frames=0; frames<62; frames++) {
+
+ trace_pkt_t *trace_pkt = (trace_pkt_t *)
+ &ppp_udp_pkt->data[buffer_length];
+
+ /* Read the whole trace packet */
+ sdla_peek(&card->hw, ppp_priv_area->curr_trace_addr,
+ &trace_element, sizeof(trace_element_t));
+
+ /* no data on board so exit */
+ if( trace_element.opp_flag == 0x00 )
+ break;
+
+ data_ptr = trace_element.trace_data_ptr;
+
+ /* See if there is actual data on the trace buffer */
+ if (data_ptr){
+ data_length = trace_element.trace_length;
+ }else{
+ data_length = 0;
+ ppp_udp_pkt->data[0] |= 0x02;
+ }
+
+ //FIXME: Do we need this check
+ if ((available_buffer_space - buffer_length)
+ < (sizeof(trace_element_t)+1)){
+
+ /*indicate we have more frames
+ * on board and exit
+ */
+ ppp_udp_pkt->data[0] |= 0x02;
+ break;
+ }
+
+ trace_pkt->status = trace_element.trace_type;
+ trace_pkt->time_stamp = trace_element.trace_time_stamp;
+ trace_pkt->real_length = trace_element.trace_length;
+
+ real_len = trace_element.trace_length;
+
+ if(data_ptr == 0){
+ trace_pkt->data_avail = 0x00;
+ }else{
+ /* we can take it next time */
+ if ((available_buffer_space - buffer_length)<
+ (real_len + sizeof(trace_pkt_t))){
+
+ ppp_udp_pkt->data[0] |= 0x02;
+ break;
+ }
+ trace_pkt->data_avail = 0x01;
+
+ /* get the data */
+ sdla_peek(&card->hw, data_ptr,
+ &trace_pkt->data,
+ real_len);
+ }
+ /* zero the opp flag to
+ show we got the frame */
+ buf2[0] = 0x00;
+ sdla_poke(&card->hw, ppp_priv_area->curr_trace_addr,
+ &buf2, 1);
+
+ /* now move onto the next
+ frame */
+ ppp_priv_area->curr_trace_addr += 8;
+
+ /* check if we passed the last address */
+ if ( ppp_priv_area->curr_trace_addr >=
+ ppp_priv_area->end_trace_addr){
+
+ ppp_priv_area->curr_trace_addr =
+ ppp_priv_area->start_trace_addr;
+ }
+
+ /* update buffer length and make sure its even */
+
+ if ( trace_pkt->data_avail == 0x01 ) {
+ buffer_length += real_len - 1;
+ }
+
+ /* for the header */
+ buffer_length += 8;
+
+ if( buffer_length & 0x0001 )
+ buffer_length += 1;
+ }
+
+ /* ok now set the total number of frames passed
+ in the high 5 bits */
+ ppp_udp_pkt->data[0] |= (frames << 2);
+
+ /* set the data length */
+ mbox->cmd.length = buffer_length;
+ ppp_udp_pkt->cblock.length = buffer_length;
+
+ /* set return code */
+ ppp_udp_pkt->cblock.result = 0;
+ break;
+
+ /* PPIPE_GET_IBA_DATA */
+ case PPIPE_GET_IBA_DATA:
+
+ mbox->cmd.length = 0x09;
+
+ sdla_peek(&card->hw, 0xF003, &ppp_udp_pkt->data,
+ mbox->cmd.length);
+
+ /* set the length of the data */
+ ppp_udp_pkt->cblock.length = 0x09;
+
+ /* set return code */
+ ppp_udp_pkt->cblock.result = 0x00;
+ ppp_udp_pkt->cblock.result = 0;
+ break;
+
+ /* PPIPE_FT1_READ_STATUS */
+ case PPIPE_FT1_READ_STATUS:
+ sdla_peek(&card->hw, 0xF020, &ppp_udp_pkt->data[0], 2);
+ ppp_udp_pkt->cblock.length = mbox->cmd.length = 2;
+ ppp_udp_pkt->cblock.result = 0;
+ break;
+
+ case PPIPE_FLUSH_DRIVER_STATS:
+ init_ppp_priv_struct( ppp_priv_area );
+ init_global_statistics( card );
+ mbox->cmd.length = 0;
+ ppp_udp_pkt->cblock.result = 0;
+ break;
+
+
+ case PPIPE_ROUTER_UP_TIME:
+
+ do_gettimeofday( &tv );
+ ppp_priv_area->router_up_time = tv.tv_sec -
+ ppp_priv_area->router_start_time;
+ *(unsigned long *)&ppp_udp_pkt->data = ppp_priv_area->router_up_time;
+ mbox->cmd.length = 4;
+ ppp_udp_pkt->cblock.result = 0;
+ break;
+
+ /* PPIPE_DRIVER_STATISTICS */
+ case PPIPE_DRIVER_STAT_IFSEND:
+ memcpy(&ppp_udp_pkt->data, &ppp_priv_area->if_send_stat,
+ sizeof(if_send_stat_t));
+
+
+ ppp_udp_pkt->cblock.result = 0;
+ ppp_udp_pkt->cblock.length = sizeof(if_send_stat_t);
+ mbox->cmd.length = sizeof(if_send_stat_t);
+ break;
+
+ case PPIPE_DRIVER_STAT_INTR:
+ memcpy(&ppp_udp_pkt->data, &card->statistics,
+ sizeof(global_stats_t));
+
+ memcpy(&ppp_udp_pkt->data+sizeof(global_stats_t),
+ &ppp_priv_area->rx_intr_stat,
+ sizeof(rx_intr_stat_t));
+
+ ppp_udp_pkt->cblock.result = 0;
+ ppp_udp_pkt->cblock.length = sizeof(global_stats_t)+
+ sizeof(rx_intr_stat_t);
+ mbox->cmd.length = ppp_udp_pkt->cblock.length;
+ break;
+
+ case PPIPE_DRIVER_STAT_GEN:
+ memcpy( &ppp_udp_pkt->data,
+ &ppp_priv_area->pipe_mgmt_stat,
+ sizeof(pipe_mgmt_stat_t));
+
+ memcpy(&ppp_udp_pkt->data+sizeof(pipe_mgmt_stat_t),
+ &card->statistics, sizeof(global_stats_t));
+
+ ppp_udp_pkt->cblock.result = 0;
+ ppp_udp_pkt->cblock.length = sizeof(global_stats_t)+
+ sizeof(rx_intr_stat_t);
+ mbox->cmd.length = ppp_udp_pkt->cblock.length;
+ break;
+
+
+ /* FT1 MONITOR STATUS */
+ case FT1_MONITOR_STATUS_CTRL:
+
+ /* Enable FT1 MONITOR STATUS */
+ if( ppp_udp_pkt->data[0] == 1) {
+
+ if( rCount++ != 0 ) {
+ ppp_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 1;
+ break;
+ }
+ }
+
+ /* Disable FT1 MONITOR STATUS */
+ if( ppp_udp_pkt->data[0] == 0) {
+
+ if( --rCount != 0) {
+ ppp_udp_pkt->cblock.result = 0;
+ mbox->cmd.length = 1;
+ break;
+ }
+ }
+ goto udp_dflt_cmd;
+
+ /* WARNING: FIXME: This should be fixed.
+ * The FT1 Status Ctrl doesn't have a break
+ * statment. Thus, no code must be inserted
+ * HERE: between default and above case statement */
+
+ default:
+udp_dflt_cmd:
+
+ /* it's a board command */
+ mbox->cmd.command = ppp_udp_pkt->cblock.command;
+ mbox->cmd.length = ppp_udp_pkt->cblock.length;
+
+ if(mbox->cmd.length) {
+ memcpy(&mbox->data,(unsigned char *)ppp_udp_pkt->data,
+ mbox->cmd.length);
+ }
+
+ /* run the command on the board */
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK) {
+
+ ppp_error(card, err, mbox);
+ ++ppp_priv_area->pipe_mgmt_stat.
+ UDP_PIPE_mgmt_adptr_cmnd_timeout;
+ break;
+ }
+
+ ++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_cmnd_OK;
+
+ /* copy the result back to our buffer */
+ memcpy(&ppp_udp_pkt->cblock,mbox, sizeof(cblock_t));
+
+ if(mbox->cmd.length) {
+ memcpy(&ppp_udp_pkt->data,&mbox->data,mbox->cmd.length);
+ }
+
+ } /* end of switch */
+ } /* end of else */
+
+ /* Fill UDP TTL */
+ ppp_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+ len = reply_udp(ppp_priv_area->udp_pkt_data, mbox->cmd.length);
+
+ if (ppp_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+ /* Make sure we are not already sending */
+ if (!test_bit(SEND_CRIT,&card->wandev.critical)){
+ ++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_passed_to_adptr;
+ ppp_send(card,ppp_priv_area->udp_pkt_data,len,ppp_priv_area->protocol);
+ }
+
+ } else {
+
+ /* Pass it up the stack
+ Allocate socket buffer */
+ if ((new_skb = dev_alloc_skb(len)) != NULL) {
+
+ /* copy data into new_skb */
+
+ buf = skb_put(new_skb, len);
+ memcpy(buf,ppp_priv_area->udp_pkt_data, len);
+
+ ++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_passed_to_stack;
+
+ /* Decapsulate packet and pass it up the protocol
+ stack */
+ new_skb->protocol = htons(ETH_P_IP);
+ new_skb->dev = dev;
+ new_skb->mac.raw = new_skb->data;
+ netif_rx(new_skb);
+ dev->last_rx = jiffies;
+
+ } else {
+
+ ++ppp_priv_area->pipe_mgmt_stat.UDP_PIPE_mgmt_no_socket;
+ printk(KERN_INFO "no socket buffers available!\n");
+ }
+ }
+
+ ppp_priv_area->udp_pkt_lgth = 0;
+
+ return;
+}
+
+/*=============================================================================
+ * Initial the ppp_private_area structure.
+ */
+static void init_ppp_priv_struct( ppp_private_area_t *ppp_priv_area )
+{
+
+ memset(&ppp_priv_area->if_send_stat, 0, sizeof(if_send_stat_t));
+ memset(&ppp_priv_area->rx_intr_stat, 0, sizeof(rx_intr_stat_t));
+ memset(&ppp_priv_area->pipe_mgmt_stat, 0, sizeof(pipe_mgmt_stat_t));
+}
+
+/*============================================================================
+ * Initialize Global Statistics
+ */
+static void init_global_statistics( sdla_t *card )
+{
+ memset(&card->statistics, 0, sizeof(global_stats_t));
+}
+
+/*============================================================================
+ * Initialize Receive and Transmit Buffers.
+ */
+static void init_ppp_tx_rx_buff( sdla_t *card )
+{
+ ppp508_buf_info_t* info;
+
+ if (card->hw.type == SDLA_S514) {
+
+ info = (void*)(card->hw.dpmbase + PPP514_BUF_OFFS);
+
+ card->u.p.txbuf_base = (void*)(card->hw.dpmbase +
+ info->txb_ptr);
+
+ card->u.p.txbuf_last = (ppp_buf_ctl_t*)card->u.p.txbuf_base +
+ (info->txb_num - 1);
+
+ card->u.p.rxbuf_base = (void*)(card->hw.dpmbase +
+ info->rxb_ptr);
+
+ card->u.p.rxbuf_last = (ppp_buf_ctl_t*)card->u.p.rxbuf_base +
+ (info->rxb_num - 1);
+
+ } else {
+
+ info = (void*)(card->hw.dpmbase + PPP508_BUF_OFFS);
+
+ card->u.p.txbuf_base = (void*)(card->hw.dpmbase +
+ (info->txb_ptr - PPP508_MB_VECT));
+
+ card->u.p.txbuf_last = (ppp_buf_ctl_t*)card->u.p.txbuf_base +
+ (info->txb_num - 1);
+
+ card->u.p.rxbuf_base = (void*)(card->hw.dpmbase +
+ (info->rxb_ptr - PPP508_MB_VECT));
+
+ card->u.p.rxbuf_last = (ppp_buf_ctl_t*)card->u.p.rxbuf_base +
+ (info->rxb_num - 1);
+ }
+
+ card->u.p.txbuf_next = (unsigned long*)&info->txb_nxt;
+ card->u.p.rxbuf_next = (unsigned long*)&info->rxb1_ptr;
+
+ card->u.p.rx_base = info->rxb_base;
+ card->u.p.rx_top = info->rxb_end;
+
+ card->u.p.txbuf = card->u.p.txbuf_base;
+ card->rxmb = card->u.p.rxbuf_base;
+
+}
+
+/*=============================================================================
+ * Read Connection Information (ie for Remote IP address assginment).
+ * Called when ppp interface connected.
+ */
+static int read_info( sdla_t *card )
+{
+ struct net_device *dev = card->wandev.dev;
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ int err;
+
+ struct ifreq if_info;
+ struct sockaddr_in *if_data1, *if_data2;
+ mm_segment_t fs;
+
+ /* Set Local and remote addresses */
+ memset(&if_info, 0, sizeof(if_info));
+ strcpy(if_info.ifr_name, dev->name);
+
+
+ fs = get_fs();
+ set_fs(get_ds()); /* get user space block */
+
+ /* Change the local and remote ip address of the interface.
+ * This will also add in the destination route.
+ */
+ if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+ if_data1->sin_addr.s_addr = ppp_priv_area->ip_local;
+ if_data1->sin_family = AF_INET;
+ err = devinet_ioctl( SIOCSIFADDR, &if_info );
+ if_data2 = (struct sockaddr_in *)&if_info.ifr_dstaddr;
+ if_data2->sin_addr.s_addr = ppp_priv_area->ip_remote;
+ if_data2->sin_family = AF_INET;
+ err = devinet_ioctl( SIOCSIFDSTADDR, &if_info );
+
+ set_fs(fs); /* restore old block */
+
+ if (err) {
+ printk (KERN_INFO "%s: Adding of route failed: %i\n",
+ card->devname,err);
+ printk (KERN_INFO "%s: Local : %u.%u.%u.%u\n",
+ card->devname,NIPQUAD(ppp_priv_area->ip_local));
+ printk (KERN_INFO "%s: Remote: %u.%u.%u.%u\n",
+ card->devname,NIPQUAD(ppp_priv_area->ip_remote));
+ }
+ return err;
+}
+
+/*=============================================================================
+ * Remove Dynamic Route.
+ * Called when ppp interface disconnected.
+ */
+
+static void remove_route( sdla_t *card )
+{
+
+ struct net_device *dev = card->wandev.dev;
+ long ip_addr;
+ int err;
+
+ mm_segment_t fs;
+ struct ifreq if_info;
+ struct sockaddr_in *if_data1;
+ struct in_device *in_dev = dev->ip_ptr;
+ struct in_ifaddr *ifa = in_dev->ifa_list;
+
+ ip_addr = ifa->ifa_local;
+
+ /* Set Local and remote addresses */
+ memset(&if_info, 0, sizeof(if_info));
+ strcpy(if_info.ifr_name, dev->name);
+
+ fs = get_fs();
+ set_fs(get_ds()); /* get user space block */
+
+ /* Change the local ip address of the interface to 0.
+ * This will also delete the destination route.
+ */
+ if_data1 = (struct sockaddr_in *)&if_info.ifr_addr;
+ if_data1->sin_addr.s_addr = 0;
+ if_data1->sin_family = AF_INET;
+ err = devinet_ioctl( SIOCSIFADDR, &if_info );
+
+ set_fs(fs); /* restore old block */
+
+
+ if (err) {
+ printk (KERN_INFO "%s: Deleting dynamic route failed %d!\n",
+ card->devname, err);
+ return;
+ }else{
+ printk (KERN_INFO "%s: PPP Deleting dynamic route %u.%u.%u.%u successfuly\n",
+ card->devname, NIPQUAD(ip_addr));
+ }
+ return;
+}
+
+/*=============================================================================
+ * Perform the Interrupt Test by running the READ_CODE_VERSION command MAX_INTR
+ * _TEST_COUNTER times.
+ */
+static int intr_test( sdla_t *card )
+{
+ ppp_mbox_t *mb = card->mbox;
+ int err,i;
+
+ err = ppp_set_intr_mode( card, 0x08 );
+
+ if (err == CMD_OK) {
+
+ for (i = 0; i < MAX_INTR_TEST_COUNTER; i ++) {
+ /* Run command READ_CODE_VERSION */
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.length = 0;
+ mb->cmd.command = PPP_READ_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+ if (err != CMD_OK)
+ ppp_error(card, err, mb);
+ }
+ }
+ else return err;
+
+ err = ppp_set_intr_mode( card, 0 );
+ if (err != CMD_OK)
+ return err;
+
+ return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. DRVSTATS or PTPIPEAB ?
+ */
+static int udp_pkt_type( struct sk_buff *skb, sdla_t *card )
+{
+ unsigned char *sendpacket;
+ unsigned char buf2[5];
+ ppp_udp_pkt_t *ppp_udp_pkt = (ppp_udp_pkt_t *)skb->data;
+
+ sendpacket = skb->data;
+ memcpy(&buf2, &card->wandev.udp_port, 2);
+
+ if( ppp_udp_pkt->ip_pkt.ver_inet_hdr_length == 0x45 && /* IP packet */
+ sendpacket[9] == 0x11 && /* UDP packet */
+ sendpacket[22] == buf2[1] && /* UDP Port */
+ sendpacket[23] == buf2[0] &&
+ sendpacket[36] == 0x01 ) {
+
+ if ( sendpacket[28] == 0x50 && /* PTPIPEAB: Signature */
+ sendpacket[29] == 0x54 &&
+ sendpacket[30] == 0x50 &&
+ sendpacket[31] == 0x49 &&
+ sendpacket[32] == 0x50 &&
+ sendpacket[33] == 0x45 &&
+ sendpacket[34] == 0x41 &&
+ sendpacket[35] == 0x42 ){
+
+ return UDP_PTPIPE_TYPE;
+
+ } else if(sendpacket[28] == 0x44 && /* DRVSTATS: Signature */
+ sendpacket[29] == 0x52 &&
+ sendpacket[30] == 0x56 &&
+ sendpacket[31] == 0x53 &&
+ sendpacket[32] == 0x54 &&
+ sendpacket[33] == 0x41 &&
+ sendpacket[34] == 0x54 &&
+ sendpacket[35] == 0x53 ){
+
+ return UDP_DRVSTATS_TYPE;
+
+ } else
+ return UDP_INVALID_TYPE;
+
+ } else
+ return UDP_INVALID_TYPE;
+
+}
+
+/*============================================================================
+ * Check to see if the packet to be transmitted contains a broadcast or
+ * multicast source IP address.
+ */
+
+static int chk_bcast_mcast_addr(sdla_t *card, struct net_device* dev,
+ struct sk_buff *skb)
+{
+ u32 src_ip_addr;
+ u32 broadcast_ip_addr = 0;
+ struct in_device *in_dev;
+
+ /* read the IP source address from the outgoing packet */
+ src_ip_addr = *(u32 *)(skb->data + 12);
+
+ /* read the IP broadcast address for the device */
+ in_dev = dev->ip_ptr;
+ if(in_dev != NULL) {
+ struct in_ifaddr *ifa= in_dev->ifa_list;
+ if(ifa != NULL)
+ broadcast_ip_addr = ifa->ifa_broadcast;
+ else
+ return 0;
+ }
+
+ /* check if the IP Source Address is a Broadcast address */
+ if((dev->flags & IFF_BROADCAST) && (src_ip_addr == broadcast_ip_addr)) {
+ printk(KERN_INFO "%s: Broadcast Source Address silently discarded\n",
+ card->devname);
+ return 1;
+ }
+
+ /* check if the IP Source Address is a Multicast address */
+ if((ntohl(src_ip_addr) >= 0xE0000001) &&
+ (ntohl(src_ip_addr) <= 0xFFFFFFFE)) {
+ printk(KERN_INFO "%s: Multicast Source Address silently discarded\n",
+ card->devname);
+ return 1;
+ }
+
+ return 0;
+}
+
+void s508_lock (sdla_t *card, unsigned long *smp_flags)
+{
+ spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+}
+
+void s508_unlock (sdla_t *card, unsigned long *smp_flags)
+{
+ spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+static int read_connection_info (sdla_t *card)
+{
+ ppp_mbox_t *mb = card->mbox;
+ struct net_device *dev = card->wandev.dev;
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+ ppp508_connect_info_t *ppp508_connect_info;
+ int err;
+
+ memset(&mb->cmd, 0, sizeof(ppp_cmd_t));
+ mb->cmd.length = 0;
+ mb->cmd.command = PPP_GET_CONNECTION_INFO;
+ err = sdla_exec(mb) ? mb->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK) {
+ ppp_error(card, err, mb);
+ ppp_priv_area->ip_remote = 0;
+ ppp_priv_area->ip_local = 0;
+ }
+ else {
+ ppp508_connect_info = (ppp508_connect_info_t *)mb->data;
+ ppp_priv_area->ip_remote = ppp508_connect_info->ip_remote;
+ ppp_priv_area->ip_local = ppp508_connect_info->ip_local;
+
+ NEX_PRINTK(KERN_INFO "READ CONNECTION GOT IP ADDRESS %x, %x\n",
+ ppp_priv_area->ip_remote,
+ ppp_priv_area->ip_local);
+ }
+
+ return err;
+}
+
+/*===============================================================================
+ * config_ppp
+ *
+ * Configure the ppp protocol and enable communications.
+ *
+ * The if_open function binds this function to the poll routine.
+ * Therefore, this function will run every time the ppp interface
+ * is brought up.
+ *
+ * If the communications are not enabled, proceed to configure
+ * the card and enable communications.
+ *
+ * If the communications are enabled, it means that the interface
+ * was shutdown by ether the user or driver. In this case, we
+ * have to check that the IP addresses have not changed. If
+ * the IP addresses changed, we have to reconfigure the firmware
+ * and update the changed IP addresses. Otherwise, just exit.
+ */
+static int config_ppp (sdla_t *card)
+{
+
+ struct net_device *dev = card->wandev.dev;
+ ppp_flags_t *flags = card->flags;
+ ppp_private_area_t *ppp_priv_area = dev->priv;
+
+ if (card->u.p.comm_enabled){
+
+ if (ppp_priv_area->ip_local_tmp != ppp_priv_area->ip_local ||
+ ppp_priv_area->ip_remote_tmp != ppp_priv_area->ip_remote){
+
+ /* The IP addersses have changed, we must
+ * stop the communications and reconfigure
+ * the card. Reason: the firmware must know
+ * the local and remote IP addresses. */
+ disable_comm(card);
+ wanpipe_set_state(card, WAN_DISCONNECTED);
+ printk(KERN_INFO
+ "%s: IP addresses changed!\n",
+ card->devname);
+ printk(KERN_INFO "%s: Restarting communications ...\n",
+ card->devname);
+ }else{
+ /* IP addresses are the same and the link is up,
+ * we don't have to do anything here. Therefore, exit */
+ return 0;
+ }
+ }
+
+ /* Record the new IP addreses */
+ ppp_priv_area->ip_local = ppp_priv_area->ip_local_tmp;
+ ppp_priv_area->ip_remote = ppp_priv_area->ip_remote_tmp;
+
+ if (config508(dev, card)){
+ printk(KERN_INFO "%s: Failed to configure PPP device\n",
+ card->devname);
+ return 0;
+ }
+
+ if (ppp_set_intr_mode(card, PPP_INTR_RXRDY|
+ PPP_INTR_TXRDY|
+ PPP_INTR_MODEM|
+ PPP_INTR_DISC |
+ PPP_INTR_OPEN |
+ PPP_INTR_DROP_DTR |
+ PPP_INTR_TIMER)) {
+
+ printk(KERN_INFO "%s: Failed to configure board interrupts !\n",
+ card->devname);
+ return 0;
+ }
+
+ /* Turn off the transmit and timer interrupt */
+ flags->imask &= ~(PPP_INTR_TXRDY | PPP_INTR_TIMER) ;
+
+
+ /* If you are not the authenticator and any one of the protocol is
+ * enabled then we call the set_out_bound_authentication.
+ */
+ if ( !card->u.p.authenticator && (ppp_priv_area->pap || ppp_priv_area->chap)) {
+ if ( ppp_set_outbnd_auth(card, ppp_priv_area) ){
+ printk(KERN_INFO "%s: Outbound authentication failed !\n",
+ card->devname);
+ return 0;
+ }
+ }
+
+ /* If you are the authenticator and any one of the protocol is enabled
+ * then we call the set_in_bound_authentication.
+ */
+ if (card->u.p.authenticator && (ppp_priv_area->pap || ppp_priv_area->chap)){
+ if (ppp_set_inbnd_auth(card, ppp_priv_area)){
+ printk(KERN_INFO "%s: Inbound authentication failed !\n",
+ card->devname);
+ return 0;
+ }
+ }
+
+ /* If we fail to enable communications here it's OK,
+ * since the DTR timer will cause a disconnected, which
+ * will retrigger communication in timer_intr() */
+ if (ppp_comm_enable(card) == CMD_OK) {
+ wanpipe_set_state(card, WAN_CONNECTING);
+ init_ppp_tx_rx_buff(card);
+ }
+
+ return 0;
+}
+
+/*============================================================
+ * ppp_poll
+ *
+ * Rationale:
+ * We cannot manipulate the routing tables, or
+ * ip addresses withing the interrupt. Therefore
+ * we must perform such actons outside an interrupt
+ * at a later time.
+ *
+ * Description:
+ * PPP polling routine, responsible for
+ * shutting down interfaces upon disconnect
+ * and adding/removing routes.
+ *
+ * Usage:
+ * This function is executed for each ppp
+ * interface through a tq_schedule bottom half.
+ *
+ * trigger_ppp_poll() function is used to kick
+ * the ppp_poll routine.
+ */
+static void ppp_poll(struct net_device *dev)
+{
+ ppp_private_area_t *ppp_priv_area;
+ sdla_t *card;
+ u8 check_gateway=0;
+ ppp_flags_t *flags;
+
+ if (!dev || (ppp_priv_area = dev->priv) == NULL)
+ return;
+
+ card = ppp_priv_area->card;
+ flags = card->flags;
+
+ /* Shutdown is in progress, stop what you are
+ * doing and get out */
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ return;
+ }
+
+ /* if_open() function has triggered the polling routine
+ * to determine the configured IP addresses. Once the
+ * addresses are found, trigger the chdlc configuration */
+ if (test_bit(0,&ppp_priv_area->config_ppp)){
+
+ ppp_priv_area->ip_local_tmp = get_ip_address(dev,WAN_LOCAL_IP);
+ ppp_priv_area->ip_remote_tmp = get_ip_address(dev,WAN_POINTOPOINT_IP);
+
+ if (ppp_priv_area->ip_local_tmp == ppp_priv_area->ip_remote_tmp &&
+ card->u.p.ip_mode == WANOPT_PPP_HOST){
+
+ if (++ppp_priv_area->ip_error > MAX_IP_ERRORS){
+ printk(KERN_INFO "\n%s: --- WARNING ---\n",
+ card->devname);
+ printk(KERN_INFO "%s: The local IP address is the same as the\n",
+ card->devname);
+ printk(KERN_INFO "%s: Point-to-Point IP address.\n",
+ card->devname);
+ printk(KERN_INFO "%s: --- WARNING ---\n\n",
+ card->devname);
+ }else{
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ ppp_priv_area->poll_delay_timer.expires = jiffies+HZ;
+ add_timer(&ppp_priv_area->poll_delay_timer);
+ return;
+ }
+ }
+
+ ppp_priv_area->timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+ flags->imask |= PPP_INTR_TIMER;
+ ppp_priv_area->ip_error=0;
+
+ clear_bit(0,&ppp_priv_area->config_ppp);
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ return;
+ }
+
+ /* Dynamic interface implementation, as well as dynamic
+ * routing. */
+
+ switch (card->wandev.state) {
+
+ case WAN_DISCONNECTED:
+
+ /* If the dynamic interface configuration is on, and interface
+ * is up, then bring down the netowrk interface */
+
+ if (test_bit(DYN_OPT_ON,&ppp_priv_area->interface_down) &&
+ !test_bit(DEV_DOWN,&ppp_priv_area->interface_down) &&
+ card->wandev.dev->flags & IFF_UP){
+
+ printk(KERN_INFO "%s: Interface %s down.\n",
+ card->devname,card->wandev.dev->name);
+ change_dev_flags(card->wandev.dev,
+ (card->wandev.dev->flags&~IFF_UP));
+ set_bit(DEV_DOWN,&ppp_priv_area->interface_down);
+ }else{
+ /* We need to check if the local IP address is
+ * zero. If it is, we shouldn't try to remove it.
+ * For some reason the kernel crashes badly if
+ * we try to remove the route twice */
+
+ if (card->wandev.dev->flags & IFF_UP &&
+ get_ip_address(card->wandev.dev,WAN_LOCAL_IP) &&
+ card->u.p.ip_mode == WANOPT_PPP_PEER){
+
+ remove_route(card);
+ }
+ }
+ break;
+
+ case WAN_CONNECTED:
+
+ /* In SMP machine this code can execute before the interface
+ * comes up. In this case, we must make sure that we do not
+ * try to bring up the interface before dev_open() is finished */
+
+
+ /* DEV_DOWN will be set only when we bring down the interface
+ * for the very first time. This way we know that it was us
+ * that brought the interface down */
+
+ if (test_bit(DYN_OPT_ON,&ppp_priv_area->interface_down) &&
+ test_bit(DEV_DOWN, &ppp_priv_area->interface_down) &&
+ !(card->wandev.dev->flags & IFF_UP)){
+
+ printk(KERN_INFO "%s: Interface %s up.\n",
+ card->devname,card->wandev.dev->name);
+
+ change_dev_flags(card->wandev.dev,(card->wandev.dev->flags|IFF_UP));
+ clear_bit(DEV_DOWN,&ppp_priv_area->interface_down);
+ check_gateway=1;
+ }
+
+ if ((card->u.p.ip_mode == WANOPT_PPP_PEER) &&
+ test_bit(1,&Read_connection_info)) {
+
+ process_route(card);
+ clear_bit(1,&Read_connection_info);
+ check_gateway=1;
+ }
+
+ if (ppp_priv_area->gateway && check_gateway)
+ add_gateway(card,dev);
+
+ break;
+ }
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ return;
+}
+
+/*============================================================
+ * trigger_ppp_poll
+ *
+ * Description:
+ * Add a ppp_poll() task into a tq_scheduler bh handler
+ * for a specific interface. This will kick
+ * the ppp_poll() routine at a later time.
+ *
+ * Usage:
+ * Interrupts use this to defer a taks to
+ * a polling routine.
+ *
+ */
+
+static void trigger_ppp_poll(struct net_device *dev)
+{
+ ppp_private_area_t *ppp_priv_area;
+ if ((ppp_priv_area=dev->priv) != NULL){
+
+ sdla_t *card = ppp_priv_area->card;
+
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ return;
+ }
+
+ if (test_and_set_bit(POLL_CRIT,&card->wandev.critical)){
+ return;
+ }
+
+ schedule_work(&ppp_priv_area->poll_work);
+ }
+ return;
+}
+
+static void ppp_poll_delay (unsigned long dev_ptr)
+{
+ struct net_device *dev = (struct net_device *)dev_ptr;
+ trigger_ppp_poll(dev);
+}
+
+/*============================================================
+ * detect_and_fix_tx_bug
+ *
+ * Description:
+ * On connect, if the board tx buffer ptr is not the same
+ * as the driver tx buffer ptr, we found a firmware bug.
+ * Report the bug to the above layer. To fix the
+ * error restart communications again.
+ *
+ * Usage:
+ *
+ */
+
+static int detect_and_fix_tx_bug (sdla_t *card)
+{
+ if (((unsigned long)card->u.p.txbuf_base&0xFFF) != ((*card->u.p.txbuf_next)&0xFFF)){
+ NEX_PRINTK(KERN_INFO "Major Error, Fix the bug\n");
+ return 1;
+ }
+ return 0;
+}
+
+MODULE_LICENSE("GPL");
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdla_x25.c b/drivers/net/wan/sdla_x25.c
new file mode 100644
index 00000000000..3a93d2fd4fb
--- /dev/null
+++ b/drivers/net/wan/sdla_x25.c
@@ -0,0 +1,5496 @@
+/*****************************************************************************
+* sdla_x25.c WANPIPE(tm) Multiprotocol WAN Link Driver. X.25 module.
+*
+* Author: Nenad Corbic <ncorbic@sangoma.com>
+*
+* Copyright: (c) 1995-2001 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Apr 03, 2001 Nenad Corbic o Fixed the rx_skb=NULL bug in x25 in rx_intr().
+* Dec 26, 2000 Nenad Corbic o Added a new polling routine, that uses
+* a kernel timer (more efficient).
+* Dec 25, 2000 Nenad Corbic o Updated for 2.4.X kernel
+* Jul 26, 2000 Nenad Corbic o Increased the local packet buffering
+* for API to 4096+header_size.
+* Jul 17, 2000 Nenad Corbic o Fixed the x25 startup bug. Enable
+* communications only after all interfaces
+* come up. HIGH SVC/PVC is used to calculate
+* the number of channels.
+* Enable protocol only after all interfaces
+* are enabled.
+* Jul 10, 2000 Nenad Corbic o Fixed the M_BIT bug.
+* Apr 25, 2000 Nenad Corbic o Pass Modem messages to the API.
+* Disable idle timeout in X25 API.
+* Apr 14, 2000 Nenad Corbic o Fixed: Large LCN number support.
+* Maximum LCN number is 4095.
+* Maximum number of X25 channels is 255.
+* Apr 06, 2000 Nenad Corbic o Added SMP Support.
+* Mar 29, 2000 Nenad Corbic o Added support for S514 PCI Card
+* Mar 23, 2000 Nenad Corbic o Improved task queue, BH handling.
+* Mar 14, 2000 Nenad Corbic o Updated Protocol Violation handling
+* routines. Bug Fix.
+* Mar 10, 2000 Nenad Corbic o Bug Fix: corrupted mbox recovery.
+* Mar 09, 2000 Nenad Corbic o Fixed the auto HDLC bug.
+* Mar 08, 2000 Nenad Corbic o Fixed LAPB HDLC startup problems.
+* Application must bring the link up
+* before tx/rx, and bring the
+* link down on close().
+* Mar 06, 2000 Nenad Corbic o Added an option for logging call setup
+* information.
+* Feb 29, 2000 Nenad Corbic o Added support for LAPB HDLC API
+* Feb 25, 2000 Nenad Corbic o Fixed the modem failure handling.
+* No Modem OOB message will be passed
+* to the user.
+* Feb 21, 2000 Nenad Corbic o Added Xpipemon Debug Support
+* Dec 30, 1999 Nenad Corbic o Socket based X25API
+* Sep 17, 1998 Jaspreet Singh o Updates for 2.2.X kernel
+* Mar 15, 1998 Alan Cox o 2.1.x porting
+* Dec 19, 1997 Jaspreet Singh o Added multi-channel IPX support
+* Nov 27, 1997 Jaspreet Singh o Added protection against enabling of irqs
+* when they are disabled.
+* Nov 17, 1997 Farhan Thawar o Added IPX support
+* o Changed if_send() to now buffer packets when
+* the board is busy
+* o Removed queueing of packets via the polling
+* routing
+* o Changed if_send() critical flags to properly
+* handle race conditions
+* Nov 06, 1997 Farhan Thawar o Added support for SVC timeouts
+* o Changed PVC encapsulation to ETH_P_IP
+* Jul 21, 1997 Jaspreet Singh o Fixed freeing up of buffers using kfree()
+* when packets are received.
+* Mar 11, 1997 Farhan Thawar Version 3.1.1
+* o added support for V35
+* o changed if_send() to return 0 if
+* wandev.critical() is true
+* o free socket buffer in if_send() if
+* returning 0
+* o added support for single '@' address to
+* accept all incoming calls
+* o fixed bug in set_chan_state() to disconnect
+* Jan 15, 1997 Gene Kozin Version 3.1.0
+* o implemented exec() entry point
+* Jan 07, 1997 Gene Kozin Initial version.
+*****************************************************************************/
+
+/*======================================================
+ * Includes
+ *=====================================================*/
+
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/ctype.h>
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+#include <linux/workqueue.h>
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <asm/atomic.h>
+#include <linux/delay.h> /* Experimental delay */
+
+#include <asm/uaccess.h>
+
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/sdla_x25.h> /* X.25 firmware API definitions */
+#include <linux/if_wanpipe_common.h>
+#include <linux/if_wanpipe.h>
+
+
+/*======================================================
+ * Defines & Macros
+ *=====================================================*/
+
+
+#define CMD_OK 0 /* normal firmware return code */
+#define CMD_TIMEOUT 0xFF /* firmware command timed out */
+#define MAX_CMD_RETRY 10 /* max number of firmware retries */
+
+#define X25_CHAN_MTU 4096 /* unfragmented logical channel MTU */
+#define X25_HRDHDR_SZ 7 /* max encapsulation header size */
+#define X25_CONCT_TMOUT (90*HZ) /* link connection timeout */
+#define X25_RECON_TMOUT (10*HZ) /* link connection timeout */
+#define CONNECT_TIMEOUT (90*HZ) /* link connection timeout */
+#define HOLD_DOWN_TIME (30*HZ) /* link hold down time */
+#define MAX_BH_BUFF 10
+#define M_BIT 0x01
+
+//#define PRINT_DEBUG 1
+#ifdef PRINT_DEBUG
+#define DBG_PRINTK(format, a...) printk(format, ## a)
+#else
+#define DBG_PRINTK(format, a...)
+#endif
+
+#define TMR_INT_ENABLED_POLL_ACTIVE 0x01
+#define TMR_INT_ENABLED_POLL_CONNECT_ON 0x02
+#define TMR_INT_ENABLED_POLL_CONNECT_OFF 0x04
+#define TMR_INT_ENABLED_POLL_DISCONNECT 0x08
+#define TMR_INT_ENABLED_CMD_EXEC 0x10
+#define TMR_INT_ENABLED_UPDATE 0x20
+#define TMR_INT_ENABLED_UDP_PKT 0x40
+
+#define MAX_X25_ADDR_SIZE 16
+#define MAX_X25_DATA_SIZE 129
+#define MAX_X25_FACL_SIZE 110
+
+#define TRY_CMD_AGAIN 2
+#define DELAY_RESULT 1
+#define RETURN_RESULT 0
+
+#define DCD(x) (x & 0x03 ? "HIGH" : "LOW")
+#define CTS(x) (x & 0x05 ? "HIGH" : "LOW")
+
+
+/* Driver will not write log messages about
+ * modem status if defined.*/
+#define MODEM_NOT_LOG 1
+
+/*====================================================
+ * For IPXWAN
+ *===================================================*/
+
+#define CVHexToAscii(b) (((unsigned char)(b) > (unsigned char)9) ? ((unsigned char)'A' + ((unsigned char)(b) - (unsigned char)10)) : ((unsigned char)'0' + (unsigned char)(b)))
+
+
+/*====================================================
+ * MEMORY DEBUGGING FUNCTION
+ *====================================================
+
+#define KMEM_SAFETYZONE 8
+
+static void * dbg_kmalloc(unsigned int size, int prio, int line) {
+ int i = 0;
+ void * v = kmalloc(size+sizeof(unsigned int)+2*KMEM_SAFETYZONE*8,prio);
+ char * c1 = v;
+ c1 += sizeof(unsigned int);
+ *((unsigned int *)v) = size;
+
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ c1[0] = 'D'; c1[1] = 'E'; c1[2] = 'A'; c1[3] = 'D';
+ c1[4] = 'B'; c1[5] = 'E'; c1[6] = 'E'; c1[7] = 'F';
+ c1 += 8;
+ }
+ c1 += size;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ c1[0] = 'M'; c1[1] = 'U'; c1[2] = 'N'; c1[3] = 'G';
+ c1[4] = 'W'; c1[5] = 'A'; c1[6] = 'L'; c1[7] = 'L';
+ c1 += 8;
+ }
+ v = ((char *)v) + sizeof(unsigned int) + KMEM_SAFETYZONE*8;
+ printk(KERN_INFO "line %d kmalloc(%d,%d) = %p\n",line,size,prio,v);
+ return v;
+}
+static void dbg_kfree(void * v, int line) {
+ unsigned int * sp = (unsigned int *)(((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8));
+ unsigned int size = *sp;
+ char * c1 = ((char *)v) - KMEM_SAFETYZONE*8;
+ int i = 0;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ if ( c1[0] != 'D' || c1[1] != 'E' || c1[2] != 'A' || c1[3] != 'D'
+ || c1[4] != 'B' || c1[5] != 'E' || c1[6] != 'E' || c1[7] != 'F') {
+ printk(KERN_INFO "kmalloced block at %p has been corrupted (underrun)!\n",v);
+ printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+ c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+ }
+ c1 += 8;
+ }
+ c1 += size;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ if ( c1[0] != 'M' || c1[1] != 'U' || c1[2] != 'N' || c1[3] != 'G'
+ || c1[4] != 'W' || c1[5] != 'A' || c1[6] != 'L' || c1[7] != 'L'
+ ) {
+ printk(KERN_INFO "kmalloced block at %p has been corrupted (overrun):\n",v);
+ printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+ c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+ }
+ c1 += 8;
+ }
+ printk(KERN_INFO "line %d kfree(%p)\n",line,v);
+ v = ((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8);
+ kfree(v);
+}
+
+#define kmalloc(x,y) dbg_kmalloc(x,y,__LINE__)
+#define kfree(x) dbg_kfree(x,__LINE__)
+
+==============================================================*/
+
+
+
+/*===============================================
+ * Data Structures
+ *===============================================*/
+
+
+/*========================================================
+ * Name: x25_channel
+ *
+ * Purpose: To hold private informaton for each
+ * logical channel.
+ *
+ * Rationale: Per-channel debugging is possible if each
+ * channel has its own private area.
+ *
+ * Assumptions:
+ *
+ * Description: This is an extention of the struct net_device
+ * we create for each network interface to keep
+ * the rest of X.25 channel-specific data.
+ *
+ * Construct: Typedef
+ */
+typedef struct x25_channel
+{
+ wanpipe_common_t common; /* common area for x25api and socket */
+ char name[WAN_IFNAME_SZ+1]; /* interface name, ASCIIZ */
+ char addr[WAN_ADDRESS_SZ+1]; /* media address, ASCIIZ */
+ unsigned tx_pkt_size;
+ unsigned short protocol; /* ethertype, 0 - multiplexed */
+ char drop_sequence; /* mark sequence for dropping */
+ unsigned long state_tick; /* time of the last state change */
+ unsigned idle_timeout; /* sec, before disconnecting */
+ unsigned long i_timeout_sofar; /* # of sec's we've been idle */
+ unsigned hold_timeout; /* sec, before re-connecting */
+ unsigned long tick_counter; /* counter for transmit time out */
+ char devtint; /* Weather we should dev_tint() */
+ struct sk_buff* rx_skb; /* receive socket buffer */
+ struct sk_buff* tx_skb; /* transmit socket buffer */
+
+ bh_data_t *bh_head; /* Circular buffer for x25api_bh */
+ unsigned long tq_working;
+ volatile int bh_write;
+ volatile int bh_read;
+ atomic_t bh_buff_used;
+
+ sdla_t* card; /* -> owner */
+ struct net_device *dev; /* -> bound devce */
+
+ int ch_idx;
+ unsigned char enable_IPX;
+ unsigned long network_number;
+ struct net_device_stats ifstats; /* interface statistics */
+ unsigned short transmit_length;
+ unsigned short tx_offset;
+ char transmit_buffer[X25_CHAN_MTU+sizeof(x25api_hdr_t)];
+
+ if_send_stat_t if_send_stat;
+ rx_intr_stat_t rx_intr_stat;
+ pipe_mgmt_stat_t pipe_mgmt_stat;
+
+ unsigned long router_start_time; /* Router start time in seconds */
+ unsigned long router_up_time;
+
+} x25_channel_t;
+
+/* FIXME Take this out */
+
+#ifdef NEX_OLD_CALL_INFO
+typedef struct x25_call_info
+{
+ char dest[17]; PACKED;/* ASCIIZ destination address */
+ char src[17]; PACKED;/* ASCIIZ source address */
+ char nuser; PACKED;/* number of user data bytes */
+ unsigned char user[127]; PACKED;/* user data */
+ char nfacil; PACKED;/* number of facilities */
+ struct
+ {
+ unsigned char code; PACKED;
+ unsigned char parm; PACKED;
+ } facil[64]; /* facilities */
+} x25_call_info_t;
+#else
+typedef struct x25_call_info
+{
+ char dest[MAX_X25_ADDR_SIZE] PACKED;/* ASCIIZ destination address */
+ char src[MAX_X25_ADDR_SIZE] PACKED;/* ASCIIZ source address */
+ unsigned char nuser PACKED;
+ unsigned char user[MAX_X25_DATA_SIZE] PACKED;/* user data */
+ unsigned char nfacil PACKED;
+ unsigned char facil[MAX_X25_FACL_SIZE] PACKED;
+ unsigned short lcn PACKED;
+} x25_call_info_t;
+#endif
+
+
+
+/*===============================================
+ * Private Function Prototypes
+ *==============================================*/
+
+
+/*=================================================
+ * WAN link driver entry points. These are
+ * called by the WAN router module.
+ */
+static int update(struct wan_device* wandev);
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+ wanif_conf_t* conf);
+static int del_if(struct wan_device* wandev, struct net_device* dev);
+static void disable_comm (sdla_t* card);
+static void disable_comm_shutdown(sdla_t *card);
+
+
+
+/*=================================================
+ * WANPIPE-specific entry points
+ */
+static int wpx_exec (struct sdla* card, void* u_cmd, void* u_data);
+static void x25api_bh(struct net_device *dev);
+static int x25api_bh_cleanup(struct net_device *dev);
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb);
+
+
+/*=================================================
+ * Network device interface
+ */
+static int if_init(struct net_device* dev);
+static int if_open(struct net_device* dev);
+static int if_close(struct net_device* dev);
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+ unsigned short type, void* daddr, void* saddr, unsigned len);
+static int if_rebuild_hdr (struct sk_buff* skb);
+static int if_send(struct sk_buff* skb, struct net_device* dev);
+static struct net_device_stats *if_stats(struct net_device* dev);
+
+static void if_tx_timeout(struct net_device *dev);
+
+/*=================================================
+ * Interrupt handlers
+ */
+static void wpx_isr (sdla_t *);
+static void rx_intr (sdla_t *);
+static void tx_intr (sdla_t *);
+static void status_intr (sdla_t *);
+static void event_intr (sdla_t *);
+static void spur_intr (sdla_t *);
+static void timer_intr (sdla_t *);
+
+static int tx_intr_send(sdla_t *card, struct net_device *dev);
+static struct net_device *move_dev_to_next(sdla_t *card,
+ struct net_device *dev);
+
+/*=================================================
+ * Background polling routines
+ */
+static void wpx_poll (sdla_t* card);
+static void poll_disconnected (sdla_t* card);
+static void poll_connecting (sdla_t* card);
+static void poll_active (sdla_t* card);
+static void trigger_x25_poll(sdla_t *card);
+static void x25_timer_routine(unsigned long data);
+
+
+
+/*=================================================
+ * X.25 firmware interface functions
+ */
+static int x25_get_version (sdla_t* card, char* str);
+static int x25_configure (sdla_t* card, TX25Config* conf);
+static int hdlc_configure (sdla_t* card, TX25Config* conf);
+static int set_hdlc_level (sdla_t* card);
+static int x25_get_err_stats (sdla_t* card);
+static int x25_get_stats (sdla_t* card);
+static int x25_set_intr_mode (sdla_t* card, int mode);
+static int x25_close_hdlc (sdla_t* card);
+static int x25_open_hdlc (sdla_t* card);
+static int x25_setup_hdlc (sdla_t* card);
+static int x25_set_dtr (sdla_t* card, int dtr);
+static int x25_get_chan_conf (sdla_t* card, x25_channel_t* chan);
+static int x25_place_call (sdla_t* card, x25_channel_t* chan);
+static int x25_accept_call (sdla_t* card, int lcn, int qdm);
+static int x25_clear_call (sdla_t* card, int lcn, int cause, int diagn);
+static int x25_send (sdla_t* card, int lcn, int qdm, int len, void* buf);
+static int x25_fetch_events (sdla_t* card);
+static int x25_error (sdla_t* card, int err, int cmd, int lcn);
+
+/*=================================================
+ * X.25 asynchronous event handlers
+ */
+static int incoming_call (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int call_accepted (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int call_cleared (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int timeout_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+static int restart_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb);
+
+
+/*=================================================
+ * Miscellaneous functions
+ */
+static int connect (sdla_t* card);
+static int disconnect (sdla_t* card);
+static struct net_device* get_dev_by_lcn(struct wan_device* wandev,
+ unsigned lcn);
+static int chan_connect(struct net_device* dev);
+static int chan_disc(struct net_device* dev);
+static void set_chan_state(struct net_device* dev, int state);
+static int chan_send(struct net_device *dev, void* buff, unsigned data_len,
+ unsigned char tx_intr);
+static unsigned char bps_to_speed_code (unsigned long bps);
+static unsigned int dec_to_uint (unsigned char* str, int len);
+static unsigned int hex_to_uint (unsigned char*, int);
+static void parse_call_info (unsigned char*, x25_call_info_t*);
+static struct net_device *find_channel(sdla_t *card, unsigned lcn);
+static void bind_lcn_to_dev(sdla_t *card, struct net_device *dev, unsigned lcn);
+static void setup_for_delayed_transmit(struct net_device *dev,
+ void *buf, unsigned len);
+
+
+/*=================================================
+ * X25 API Functions
+ */
+static int wanpipe_pull_data_in_skb(sdla_t *card, struct net_device *dev,
+ struct sk_buff **);
+static void timer_intr_exec(sdla_t *, unsigned char);
+static int execute_delayed_cmd(sdla_t *card, struct net_device *dev,
+ mbox_cmd_t *usr_cmd, char bad_cmd);
+static int api_incoming_call (sdla_t*, TX25Mbox *, int);
+static int alloc_and_init_skb_buf (sdla_t *,struct sk_buff **, int);
+static void send_delayed_cmd_result(sdla_t *card, struct net_device *dev,
+ TX25Mbox* mbox);
+static int clear_confirm_event (sdla_t *, TX25Mbox*);
+static void send_oob_msg (sdla_t *card, struct net_device *dev, TX25Mbox *mbox);
+static int timer_intr_cmd_exec(sdla_t *card);
+static void api_oob_event (sdla_t *card,TX25Mbox *mbox);
+static int check_bad_command(sdla_t *card, struct net_device *dev);
+static int channel_disconnect(sdla_t* card, struct net_device *dev);
+static void hdlc_link_down (sdla_t*);
+
+/*=================================================
+ * XPIPEMON Functions
+ */
+static int process_udp_mgmt_pkt(sdla_t *);
+static int udp_pkt_type( struct sk_buff *, sdla_t*);
+static int reply_udp( unsigned char *, unsigned int);
+static void init_x25_channel_struct( x25_channel_t *);
+static void init_global_statistics( sdla_t *);
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t *card,
+ struct net_device *dev,
+ struct sk_buff *skb, int lcn);
+static unsigned short calc_checksum (char *, int);
+
+
+
+/*=================================================
+ * IPX functions
+ */
+static void switch_net_numbers(unsigned char *, unsigned long, unsigned char);
+static int handle_IPXWAN(unsigned char *, char *, unsigned char ,
+ unsigned long , unsigned short );
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+static void S508_S514_lock(sdla_t *, unsigned long *);
+static void S508_S514_unlock(sdla_t *, unsigned long *);
+
+
+/*=================================================
+ * Global Variables
+ *=================================================*/
+
+
+
+/*=================================================
+ * Public Functions
+ *=================================================*/
+
+
+
+
+/*===================================================================
+ * wpx_init: X.25 Protocol Initialization routine.
+ *
+ * Purpose: To initialize the protocol/firmware.
+ *
+ * Rationale: This function is called by setup() function, in
+ * sdlamain.c, to dynamically setup the x25 protocol.
+ * This is the first protocol specific function, which
+ * executes once on startup.
+ *
+ * Description: This procedure initializes the x25 firmware and
+ * sets up the mailbox, transmit and receive buffer
+ * pointers. It also initializes all debugging structures
+ * and sets up the X25 environment.
+ *
+ * Sets up hardware options defined by user in [wanpipe#]
+ * section of wanpipe#.conf configuration file.
+ *
+ * At this point adapter is completely initialized
+ * and X.25 firmware is running.
+ * o read firmware version (to make sure it's alive)
+ * o configure adapter
+ * o initialize protocol-specific fields of the
+ * adapter data space.
+ *
+ * Called by: setup() function in sdlamain.c
+ *
+ * Assumptions: None
+ *
+ * Warnings: None
+ *
+ * Return: 0 o.k.
+ * < 0 failure.
+ */
+
+int wpx_init (sdla_t* card, wandev_conf_t* conf)
+{
+ union{
+ char str[80];
+ TX25Config cfg;
+ } u;
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_X25){
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id)
+ ;
+ return -EINVAL;
+ }
+
+ /* Initialize protocol-specific fields */
+ card->mbox = (void*)(card->hw.dpmbase + X25_MBOX_OFFS);
+ card->rxmb = (void*)(card->hw.dpmbase + X25_RXMBOX_OFFS);
+ card->flags = (void*)(card->hw.dpmbase + X25_STATUS_OFFS);
+
+ /* Initialize for S514 Card */
+ if(card->hw.type == SDLA_S514) {
+ card->mbox += X25_MB_VECTOR;
+ card->flags += X25_MB_VECTOR;
+ card->rxmb += X25_MB_VECTOR;
+ }
+
+
+ /* Read firmware version. Note that when adapter initializes, it
+ * clears the mailbox, so it may appear that the first command was
+ * executed successfully when in fact it was merely erased. To work
+ * around this, we execute the first command twice.
+ */
+ if (x25_get_version(card, NULL) || x25_get_version(card, u.str))
+ return -EIO;
+
+
+ /* X25 firmware can run ether in X25 or LAPB HDLC mode.
+ * Check the user defined option and configure accordingly */
+ if (conf->u.x25.LAPB_hdlc_only == WANOPT_YES){
+ if (set_hdlc_level(card) != CMD_OK){
+ return -EIO;
+ }else{
+ printk(KERN_INFO "%s: running LAP_B HDLC firmware v%s\n",
+ card->devname, u.str);
+ }
+ card->u.x.LAPB_hdlc = 1;
+ }else{
+ printk(KERN_INFO "%s: running X.25 firmware v%s\n",
+ card->devname, u.str);
+ card->u.x.LAPB_hdlc = 0;
+ }
+
+ /* Configure adapter. Here we set resonable defaults, then parse
+ * device configuration structure and set configuration options.
+ * Most configuration options are verified and corrected (if
+ * necessary) since we can't rely on the adapter to do so.
+ */
+ memset(&u.cfg, 0, sizeof(u.cfg));
+ u.cfg.t1 = 3;
+ u.cfg.n2 = 10;
+ u.cfg.autoHdlc = 1; /* automatic HDLC connection */
+ u.cfg.hdlcWindow = 7;
+ u.cfg.pktWindow = 2;
+ u.cfg.station = 1; /* DTE */
+ u.cfg.options = 0x0090; /* disable D-bit pragmatics */
+ u.cfg.ccittCompat = 1988;
+ u.cfg.t10t20 = 30;
+ u.cfg.t11t21 = 30;
+ u.cfg.t12t22 = 30;
+ u.cfg.t13t23 = 30;
+ u.cfg.t16t26 = 30;
+ u.cfg.t28 = 30;
+ u.cfg.r10r20 = 5;
+ u.cfg.r12r22 = 5;
+ u.cfg.r13r23 = 5;
+ u.cfg.responseOpt = 1; /* RR's after every packet */
+
+ if (card->u.x.LAPB_hdlc){
+ u.cfg.hdlcMTU = 1027;
+ }
+
+ if (conf->u.x25.x25_conf_opt){
+ u.cfg.options = conf->u.x25.x25_conf_opt;
+ }
+
+ if (conf->clocking != WANOPT_EXTERNAL)
+ u.cfg.baudRate = bps_to_speed_code(conf->bps);
+
+ if (conf->station != WANOPT_DTE){
+ u.cfg.station = 0; /* DCE mode */
+ }
+
+ if (conf->interface != WANOPT_RS232 ){
+ u.cfg.hdlcOptions |= 0x80; /* V35 mode */
+ }
+
+ /* adjust MTU */
+ if (!conf->mtu || (conf->mtu >= 1024))
+ card->wandev.mtu = 1024;
+ else if (conf->mtu >= 512)
+ card->wandev.mtu = 512;
+ else if (conf->mtu >= 256)
+ card->wandev.mtu = 256;
+ else if (conf->mtu >= 128)
+ card->wandev.mtu = 128;
+ else
+ card->wandev.mtu = 64;
+
+ u.cfg.defPktSize = u.cfg.pktMTU = card->wandev.mtu;
+
+ if (conf->u.x25.hi_pvc){
+ card->u.x.hi_pvc = min_t(unsigned int, conf->u.x25.hi_pvc, MAX_LCN_NUM);
+ card->u.x.lo_pvc = min_t(unsigned int, conf->u.x25.lo_pvc, card->u.x.hi_pvc);
+ }
+
+ if (conf->u.x25.hi_svc){
+ card->u.x.hi_svc = min_t(unsigned int, conf->u.x25.hi_svc, MAX_LCN_NUM);
+ card->u.x.lo_svc = min_t(unsigned int, conf->u.x25.lo_svc, card->u.x.hi_svc);
+ }
+
+ /* Figure out the total number of channels to configure */
+ card->u.x.num_of_ch = 0;
+ if (card->u.x.hi_svc != 0){
+ card->u.x.num_of_ch = (card->u.x.hi_svc - card->u.x.lo_svc) + 1;
+ }
+ if (card->u.x.hi_pvc != 0){
+ card->u.x.num_of_ch += (card->u.x.hi_pvc - card->u.x.lo_pvc) + 1;
+ }
+
+ if (card->u.x.num_of_ch == 0){
+ printk(KERN_INFO "%s: ERROR, Minimum number of PVC/SVC channels is 1 !\n"
+ "%s: Please set the Lowest/Highest PVC/SVC values !\n",
+ card->devname,card->devname);
+ return -ECHRNG;
+ }
+
+ u.cfg.loPVC = card->u.x.lo_pvc;
+ u.cfg.hiPVC = card->u.x.hi_pvc;
+ u.cfg.loTwoWaySVC = card->u.x.lo_svc;
+ u.cfg.hiTwoWaySVC = card->u.x.hi_svc;
+
+ if (conf->u.x25.hdlc_window)
+ u.cfg.hdlcWindow = min_t(unsigned int, conf->u.x25.hdlc_window, 7);
+ if (conf->u.x25.pkt_window)
+ u.cfg.pktWindow = min_t(unsigned int, conf->u.x25.pkt_window, 7);
+
+ if (conf->u.x25.t1)
+ u.cfg.t1 = min_t(unsigned int, conf->u.x25.t1, 30);
+ if (conf->u.x25.t2)
+ u.cfg.t2 = min_t(unsigned int, conf->u.x25.t2, 29);
+ if (conf->u.x25.t4)
+ u.cfg.t4 = min_t(unsigned int, conf->u.x25.t4, 240);
+ if (conf->u.x25.n2)
+ u.cfg.n2 = min_t(unsigned int, conf->u.x25.n2, 30);
+
+ if (conf->u.x25.t10_t20)
+ u.cfg.t10t20 = min_t(unsigned int, conf->u.x25.t10_t20,255);
+ if (conf->u.x25.t11_t21)
+ u.cfg.t11t21 = min_t(unsigned int, conf->u.x25.t11_t21,255);
+ if (conf->u.x25.t12_t22)
+ u.cfg.t12t22 = min_t(unsigned int, conf->u.x25.t12_t22,255);
+ if (conf->u.x25.t13_t23)
+ u.cfg.t13t23 = min_t(unsigned int, conf->u.x25.t13_t23,255);
+ if (conf->u.x25.t16_t26)
+ u.cfg.t16t26 = min_t(unsigned int, conf->u.x25.t16_t26, 255);
+ if (conf->u.x25.t28)
+ u.cfg.t28 = min_t(unsigned int, conf->u.x25.t28, 255);
+
+ if (conf->u.x25.r10_r20)
+ u.cfg.r10r20 = min_t(unsigned int, conf->u.x25.r10_r20,250);
+ if (conf->u.x25.r12_r22)
+ u.cfg.r12r22 = min_t(unsigned int, conf->u.x25.r12_r22,250);
+ if (conf->u.x25.r13_r23)
+ u.cfg.r13r23 = min_t(unsigned int, conf->u.x25.r13_r23,250);
+
+
+ if (conf->u.x25.ccitt_compat)
+ u.cfg.ccittCompat = conf->u.x25.ccitt_compat;
+
+ /* initialize adapter */
+ if (card->u.x.LAPB_hdlc){
+ if (hdlc_configure(card, &u.cfg) != CMD_OK)
+ return -EIO;
+ }else{
+ if (x25_configure(card, &u.cfg) != CMD_OK)
+ return -EIO;
+ }
+
+ if ((x25_close_hdlc(card) != CMD_OK) || /* close HDLC link */
+ (x25_set_dtr(card, 0) != CMD_OK)) /* drop DTR */
+ return -EIO;
+
+ /* Initialize protocol-specific fields of adapter data space */
+ card->wandev.bps = conf->bps;
+ card->wandev.interface = conf->interface;
+ card->wandev.clocking = conf->clocking;
+ card->wandev.station = conf->station;
+ card->isr = &wpx_isr;
+ card->poll = NULL; //&wpx_poll;
+ card->disable_comm = &disable_comm;
+ card->exec = &wpx_exec;
+ card->wandev.update = &update;
+ card->wandev.new_if = &new_if;
+ card->wandev.del_if = &del_if;
+
+ /* WARNING: This function cannot exit with an error
+ * after the change of state */
+ card->wandev.state = WAN_DISCONNECTED;
+
+ card->wandev.enable_tx_int = 0;
+ card->irq_dis_if_send_count = 0;
+ card->irq_dis_poll_count = 0;
+ card->u.x.tx_dev = NULL;
+ card->u.x.no_dev = 0;
+
+
+ /* Configure for S514 PCI Card */
+ if (card->hw.type == SDLA_S514) {
+ card->u.x.hdlc_buf_status =
+ (volatile unsigned char *)
+ (card->hw.dpmbase + X25_MB_VECTOR+ X25_MISC_HDLC_BITS);
+ }else{
+ card->u.x.hdlc_buf_status =
+ (volatile unsigned char *)(card->hw.dpmbase + X25_MISC_HDLC_BITS);
+ }
+
+ card->u.x.poll_device=NULL;
+ card->wandev.udp_port = conf->udp_port;
+
+ /* Enable or disable call setup logging */
+ if (conf->u.x25.logging == WANOPT_YES){
+ printk(KERN_INFO "%s: Enabling Call Logging.\n",
+ card->devname);
+ card->u.x.logging = 1;
+ }else{
+ card->u.x.logging = 0;
+ }
+
+ /* Enable or disable modem status reporting */
+ if (conf->u.x25.oob_on_modem == WANOPT_YES){
+ printk(KERN_INFO "%s: Enabling OOB on Modem change.\n",
+ card->devname);
+ card->u.x.oob_on_modem = 1;
+ }else{
+ card->u.x.oob_on_modem = 0;
+ }
+
+ init_global_statistics(card);
+
+ INIT_WORK(&card->u.x.x25_poll_work, (void *)wpx_poll, card);
+
+ init_timer(&card->u.x.x25_timer);
+ card->u.x.x25_timer.data = (unsigned long)card;
+ card->u.x.x25_timer.function = x25_timer_routine;
+
+ return 0;
+}
+
+/*=========================================================
+ * WAN Device Driver Entry Points
+ *========================================================*/
+
+/*============================================================
+ * Name: update(), Update device status & statistics.
+ *
+ * Purpose: To provide debugging and statitical
+ * information to the /proc file system.
+ * /proc/net/wanrouter/wanpipe#
+ *
+ * Rationale: The /proc file system is used to collect
+ * information about the kernel and drivers.
+ * Using the /proc file system the user
+ * can see exactly what the sangoma drivers are
+ * doing. And in what state they are in.
+ *
+ * Description: Collect all driver statistical information
+ * and pass it to the top laywer.
+ *
+ * Since we have to execute a debugging command,
+ * to obtain firmware statitics, we trigger a
+ * UPDATE function within the timer interrtup.
+ * We wait until the timer update is complete.
+ * Once complete return the appropriate return
+ * code to indicate that the update was successful.
+ *
+ * Called by: device_stat() in wanmain.c
+ *
+ * Assumptions:
+ *
+ * Warnings: This function will degrade the performance
+ * of the router, since it uses the mailbox.
+ *
+ * Return: 0 OK
+ * <0 Failed (or busy).
+ */
+
+static int update(struct wan_device* wandev)
+{
+ volatile sdla_t* card;
+ TX25Status* status;
+ unsigned long timeout;
+
+ /* sanity checks */
+ if ((wandev == NULL) || (wandev->private == NULL))
+ return -EFAULT;
+
+ if (wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ if (test_bit(SEND_CRIT, (void*)&wandev->critical))
+ return -EAGAIN;
+
+ if (!wandev->dev)
+ return -ENODEV;
+
+ card = wandev->private;
+ status = card->flags;
+
+ card->u.x.timer_int_enabled |= TMR_INT_ENABLED_UPDATE;
+ status->imask |= INTR_ON_TIMER;
+ timeout = jiffies;
+
+ for (;;){
+ if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_UPDATE)){
+ break;
+ }
+ if ((jiffies-timeout) > 1*HZ){
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE;
+ return -EAGAIN;
+ }
+ }
+ return 0;
+}
+
+
+/*===================================================================
+ * Name: new_if
+ *
+ * Purpose: To allocate and initialize resources for a
+ * new logical channel.
+ *
+ * Rationale: A new channel can be added dynamically via
+ * ioctl call.
+ *
+ * Description: Allocate a private channel structure, x25_channel_t.
+ * Parse the user interface options from wanpipe#.conf
+ * configuration file.
+ * Bind the private are into the network device private
+ * area pointer (dev->priv).
+ * Prepare the network device structure for registration.
+ *
+ * Called by: ROUTER_IFNEW Ioctl call, from wanrouter_ioctl()
+ * (wanmain.c)
+ *
+ * Assumptions: None
+ *
+ * Warnings: None
+ *
+ * Return: 0 Ok
+ * <0 Failed (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+ wanif_conf_t* conf)
+{
+ sdla_t* card = wandev->private;
+ x25_channel_t* chan;
+ int err = 0;
+
+ if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)){
+ printk(KERN_INFO "%s: invalid interface name!\n",
+ card->devname);
+ return -EINVAL;
+ }
+
+ if(card->wandev.new_if_cnt++ > 0 && card->u.x.LAPB_hdlc) {
+ printk(KERN_INFO "%s: Error: Running LAPB HDLC Mode !\n",
+ card->devname);
+ printk(KERN_INFO
+ "%s: Maximum number of network interfaces must be one !\n",
+ card->devname);
+ return -EEXIST;
+ }
+
+ /* allocate and initialize private data */
+ chan = kmalloc(sizeof(x25_channel_t), GFP_ATOMIC);
+ if (chan == NULL){
+ return -ENOMEM;
+ }
+
+ memset(chan, 0, sizeof(x25_channel_t));
+
+ /* Bug Fix: Seg Err on PVC startup
+ * It must be here since bind_lcn_to_dev expects
+ * it bellow */
+ dev->priv = chan;
+
+ strcpy(chan->name, conf->name);
+ chan->card = card;
+ chan->dev = dev;
+ chan->common.sk = NULL;
+ chan->common.func = NULL;
+ chan->common.rw_bind = 0;
+ chan->tx_skb = chan->rx_skb = NULL;
+
+ /* verify media address */
+ if (conf->addr[0] == '@'){ /* SVC */
+ chan->common.svc = 1;
+ strncpy(chan->addr, &conf->addr[1], WAN_ADDRESS_SZ);
+
+ /* Set channel timeouts (default if not specified) */
+ chan->idle_timeout = (conf->idle_timeout) ?
+ conf->idle_timeout : 90;
+ chan->hold_timeout = (conf->hold_timeout) ?
+ conf->hold_timeout : 10;
+
+ }else if (is_digit(conf->addr[0])){ /* PVC */
+ int lcn = dec_to_uint(conf->addr, 0);
+
+ if ((lcn >= card->u.x.lo_pvc) && (lcn <= card->u.x.hi_pvc)){
+ bind_lcn_to_dev (card, dev, lcn);
+ }else{
+ printk(KERN_ERR
+ "%s: PVC %u is out of range on interface %s!\n",
+ wandev->name, lcn, chan->name);
+ err = -EINVAL;
+ }
+ }else{
+ printk(KERN_ERR
+ "%s: invalid media address on interface %s!\n",
+ wandev->name, chan->name);
+ err = -EINVAL;
+ }
+
+ if(strcmp(conf->usedby, "WANPIPE") == 0){
+ printk(KERN_INFO "%s: Running in WANPIPE mode %s\n",
+ wandev->name, chan->name);
+ chan->common.usedby = WANPIPE;
+ chan->protocol = htons(ETH_P_IP);
+
+ }else if(strcmp(conf->usedby, "API") == 0){
+ chan->common.usedby = API;
+ printk(KERN_INFO "%s: Running in API mode %s\n",
+ wandev->name, chan->name);
+ chan->protocol = htons(X25_PROT);
+ }
+
+
+ if (err){
+ kfree(chan);
+ dev->priv = NULL;
+ return err;
+ }
+
+ chan->enable_IPX = conf->enable_IPX;
+
+ if (chan->enable_IPX)
+ chan->protocol = htons(ETH_P_IPX);
+
+ if (conf->network_number)
+ chan->network_number = conf->network_number;
+ else
+ chan->network_number = 0xDEADBEEF;
+
+ /* prepare network device data space for registration */
+ strcpy(dev->name,chan->name);
+
+ dev->init = &if_init;
+
+ init_x25_channel_struct(chan);
+
+ return 0;
+}
+
+/*===================================================================
+ * Name: del_if(), Remove a logical channel.
+ *
+ * Purpose: To dynamically remove a logical channel.
+ *
+ * Rationale: Each logical channel should be dynamically
+ * removable. This functin is called by an
+ * IOCTL_IFDEL ioctl call or shutdown().
+ *
+ * Description: Do nothing.
+ *
+ * Called by: IOCTL_IFDEL : wanrouter_ioctl() from wanmain.c
+ * shutdown() from sdlamain.c
+ *
+ * Assumptions:
+ *
+ * Warnings:
+ *
+ * Return: 0 Ok. Void function.
+ */
+
+//FIXME Del IF Should be taken out now.
+
+static int del_if(struct wan_device* wandev, struct net_device* dev)
+{
+ return 0;
+}
+
+
+/*============================================================
+ * Name: wpx_exec
+ *
+ * Description: Execute adapter interface command.
+ * This option is currently dissabled.
+ *===========================================================*/
+
+static int wpx_exec (struct sdla* card, void* u_cmd, void* u_data)
+{
+ return 0;
+}
+
+/*============================================================
+ * Name: disable_comm
+ *
+ * Description: Disable communications during shutdown.
+ * Dont check return code because there is
+ * nothing we can do about it.
+ *
+ * Warning: Dev and private areas are gone at this point.
+ *===========================================================*/
+
+static void disable_comm(sdla_t* card)
+{
+ disable_comm_shutdown(card);
+ del_timer(&card->u.x.x25_timer);
+ return;
+}
+
+
+/*============================================================
+ * Network Device Interface
+ *===========================================================*/
+
+/*===================================================================
+ * Name: if_init(), Netowrk Interface Initialization
+ *
+ * Purpose: To initialize a network interface device structure.
+ *
+ * Rationale: During network interface startup, the if_init
+ * is called by the kernel to initialize the
+ * netowrk device structure. Thus a driver
+ * can customze a network device.
+ *
+ * Description: Initialize the netowrk device call back
+ * routines. This is where we tell the kernel
+ * which function to use when it wants to send
+ * via our interface.
+ * Furthermore, we initialize the device flags,
+ * MTU and physical address of the board.
+ *
+ * Called by: Kernel (/usr/src/linux/net/core/dev.c)
+ * (dev->init())
+ *
+ * Assumptions: None
+ *
+ * Warnings: None
+ *
+ * Return: 0 Ok : Void function.
+ */
+static int if_init(struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ struct wan_device* wandev = &card->wandev;
+
+ /* Initialize device driver entry points */
+ dev->open = &if_open;
+ dev->stop = &if_close;
+ dev->hard_header = &if_header;
+ dev->rebuild_header = &if_rebuild_hdr;
+ dev->hard_start_xmit = &if_send;
+ dev->get_stats = &if_stats;
+ dev->tx_timeout = &if_tx_timeout;
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+ /* Initialize media-specific parameters */
+ dev->type = ARPHRD_PPP; /* ARP h/w type */
+ dev->flags |= IFF_POINTOPOINT;
+ dev->flags |= IFF_NOARP;
+
+ if (chan->common.usedby == API){
+ dev->mtu = X25_CHAN_MTU+sizeof(x25api_hdr_t);
+ }else{
+ dev->mtu = card->wandev.mtu;
+ }
+
+ dev->hard_header_len = X25_HRDHDR_SZ; /* media header length */
+ dev->addr_len = 2; /* hardware address length */
+
+ if (!chan->common.svc){
+ *(unsigned short*)dev->dev_addr = htons(chan->common.lcn);
+ }
+
+ /* Initialize hardware parameters (just for reference) */
+ dev->irq = wandev->irq;
+ dev->dma = wandev->dma;
+ dev->base_addr = wandev->ioport;
+ dev->mem_start = (unsigned long)wandev->maddr;
+ dev->mem_end = wandev->maddr + wandev->msize - 1;
+
+ /* Set transmit buffer queue length */
+ dev->tx_queue_len = 100;
+ SET_MODULE_OWNER(dev);
+
+ /* FIXME Why are we doing this */
+ set_chan_state(dev, WAN_DISCONNECTED);
+ return 0;
+}
+
+
+/*===================================================================
+ * Name: if_open(), Open/Bring up the Netowrk Interface
+ *
+ * Purpose: To bring up a network interface.
+ *
+ * Rationale:
+ *
+ * Description: Open network interface.
+ * o prevent module from unloading by incrementing use count
+ * o if link is disconnected then initiate connection
+ *
+ * Called by: Kernel (/usr/src/linux/net/core/dev.c)
+ * (dev->open())
+ *
+ * Assumptions: None
+ *
+ * Warnings: None
+ *
+ * Return: 0 Ok
+ * <0 Failure: Interface will not come up.
+ */
+
+static int if_open(struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ struct timeval tv;
+ unsigned long smp_flags;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ chan->tq_working = 0;
+
+ /* Initialize the workqueue */
+ INIT_WORK(&chan->common.wanpipe_work, (void *)x25api_bh, dev);
+
+ /* Allocate and initialize BH circular buffer */
+ /* Add 1 to MAX_BH_BUFF so we don't have test with (MAX_BH_BUFF-1) */
+ chan->bh_head = kmalloc((sizeof(bh_data_t)*(MAX_BH_BUFF+1)),GFP_ATOMIC);
+
+ if (chan->bh_head == NULL){
+ printk(KERN_INFO "%s: ERROR, failed to allocate memory ! BH_BUFFERS !\n",
+ card->devname);
+
+ return -ENOBUFS;
+ }
+ memset(chan->bh_head,0,(sizeof(bh_data_t)*(MAX_BH_BUFF+1)));
+ atomic_set(&chan->bh_buff_used, 0);
+
+ /* Increment the number of interfaces */
+ ++card->u.x.no_dev;
+
+ wanpipe_open(card);
+
+ /* LAPB protocol only uses one interface, thus
+ * start the protocol after it comes up. */
+ if (card->u.x.LAPB_hdlc){
+ if (card->open_cnt == 1){
+ TX25Status* status = card->flags;
+ S508_S514_lock(card, &smp_flags);
+ x25_set_intr_mode(card, INTR_ON_TIMER);
+ status->imask &= ~INTR_ON_TIMER;
+ S508_S514_unlock(card, &smp_flags);
+ }
+ }else{
+ /* X25 can have multiple interfaces thus, start the
+ * protocol once all interfaces are up */
+
+ //FIXME: There is a bug here. If interface is
+ //brought down and up, it will try to enable comm.
+ if (card->open_cnt == card->u.x.num_of_ch){
+
+ S508_S514_lock(card, &smp_flags);
+ connect(card);
+ S508_S514_unlock(card, &smp_flags);
+
+ mod_timer(&card->u.x.x25_timer, jiffies + HZ);
+ }
+ }
+ /* Device is not up until the we are in connected state */
+ do_gettimeofday( &tv );
+ chan->router_start_time = tv.tv_sec;
+
+ netif_start_queue(dev);
+
+ return 0;
+}
+
+/*===================================================================
+ * Name: if_close(), Close/Bring down the Netowrk Interface
+ *
+ * Purpose: To bring down a network interface.
+ *
+ * Rationale:
+ *
+ * Description: Close network interface.
+ * o decrement use module use count
+ *
+ * Called by: Kernel (/usr/src/linux/net/core/dev.c)
+ * (dev->close())
+ * ifconfig <name> down: will trigger the kernel
+ * which will call this function.
+ *
+ * Assumptions: None
+ *
+ * Warnings: None
+ *
+ * Return: 0 Ok
+ * <0 Failure: Interface will not exit properly.
+ */
+static int if_close(struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ unsigned long smp_flags;
+
+ netif_stop_queue(dev);
+
+ if ((chan->common.state == WAN_CONNECTED) ||
+ (chan->common.state == WAN_CONNECTING)){
+ S508_S514_lock(card, &smp_flags);
+ chan_disc(dev);
+ S508_S514_unlock(card, &smp_flags);
+ }
+
+ wanpipe_close(card);
+
+ S508_S514_lock(card, &smp_flags);
+ if (chan->bh_head){
+ int i;
+ struct sk_buff *skb;
+
+ for (i=0; i<(MAX_BH_BUFF+1); i++){
+ skb = ((bh_data_t *)&chan->bh_head[i])->skb;
+ if (skb != NULL){
+ dev_kfree_skb_any(skb);
+ }
+ }
+ kfree(chan->bh_head);
+ chan->bh_head=NULL;
+ }
+ S508_S514_unlock(card, &smp_flags);
+
+ /* If this is the last close, disconnect physical link */
+ if (!card->open_cnt){
+ S508_S514_lock(card, &smp_flags);
+ disconnect(card);
+ x25_set_intr_mode(card, 0);
+ S508_S514_unlock(card, &smp_flags);
+ }
+
+ /* Decrement the number of interfaces */
+ --card->u.x.no_dev;
+ return 0;
+}
+
+/*======================================================================
+ * Build media header.
+ * o encapsulate packet according to encapsulation type.
+ *
+ * The trick here is to put packet type (Ethertype) into 'protocol'
+ * field of the socket buffer, so that we don't forget it.
+ * If encapsulation fails, set skb->protocol to 0 and discard
+ * packet later.
+ *
+ * Return: media header length.
+ *======================================================================*/
+
+static int if_header(struct sk_buff* skb, struct net_device* dev,
+ unsigned short type, void* daddr, void* saddr,
+ unsigned len)
+{
+ x25_channel_t* chan = dev->priv;
+ int hdr_len = dev->hard_header_len;
+
+ skb->protocol = htons(type);
+ if (!chan->protocol){
+ hdr_len = wanrouter_encapsulate(skb, dev, type);
+ if (hdr_len < 0){
+ hdr_len = 0;
+ skb->protocol = htons(0);
+ }
+ }
+ return hdr_len;
+}
+
+/*===============================================================
+ * Re-build media header.
+ *
+ * Return: 1 physical address resolved.
+ * 0 physical address not resolved
+ *==============================================================*/
+
+static int if_rebuild_hdr (struct sk_buff* skb)
+{
+ struct net_device *dev = skb->dev;
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+
+ printk(KERN_INFO "%s: rebuild_header() called for interface %s!\n",
+ card->devname, dev->name);
+ return 1;
+}
+
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ /* If our device stays busy for at least 5 seconds then we will
+ * kick start the device by making dev->tbusy = 0. We expect
+ * that our device never stays busy more than 5 seconds. So this
+ * is only used as a last resort.
+ */
+
+ ++chan->if_send_stat.if_send_tbusy_timeout;
+ printk (KERN_INFO "%s: Transmit timed out on %s\n",
+ card->devname, dev->name);
+ netif_wake_queue (dev);
+}
+
+
+/*=========================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission).
+ * o check link state. If link is not up, then drop the packet.
+ * o check channel status. If it's down then initiate a call.
+ * o pass a packet to corresponding WAN device.
+ * o free socket buffer
+ *
+ * Return: 0 complete (socket buffer must be freed)
+ * non-0 packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ * bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ * protocol stack and can be used for flow control with protocol layer.
+ *
+ *========================================================================*/
+
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ TX25Status* status = card->flags;
+ int udp_type;
+ unsigned long smp_flags=0;
+
+ ++chan->if_send_stat.if_send_entry;
+
+ netif_stop_queue(dev);
+
+ /* No need to check frame length, since socket code
+ * will perform the check for us */
+
+ chan->tick_counter = jiffies;
+
+ /* Critical region starts here */
+ S508_S514_lock(card, &smp_flags);
+
+ if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)){
+ printk(KERN_INFO "Hit critical in if_send()! %lx\n",card->wandev.critical);
+ goto if_send_crit_exit;
+ }
+
+ udp_type = udp_pkt_type(skb, card);
+
+ if(udp_type != UDP_INVALID_TYPE) {
+
+ if(store_udp_mgmt_pkt(udp_type, UDP_PKT_FRM_STACK, card, dev, skb,
+ chan->common.lcn)) {
+
+ status->imask |= INTR_ON_TIMER;
+ if (udp_type == UDP_XPIPE_TYPE){
+ chan->if_send_stat.if_send_PIPE_request++;
+ }
+ }
+ netif_start_queue(dev);
+ clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+ S508_S514_unlock(card, &smp_flags);
+ return 0;
+ }
+
+ if (chan->transmit_length){
+ //FIXME: This check doesn't make sense any more
+ if (chan->common.state != WAN_CONNECTED){
+ chan->transmit_length=0;
+ atomic_set(&chan->common.driver_busy,0);
+ }else{
+ netif_stop_queue(dev);
+ ++card->u.x.tx_interrupts_pending;
+ status->imask |= INTR_ON_TX_FRAME;
+ clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+ S508_S514_unlock(card, &smp_flags);
+ return 1;
+ }
+ }
+
+ if (card->wandev.state != WAN_CONNECTED){
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ ++chan->if_send_stat.if_send_wan_disconnected;
+
+ }else if ( chan->protocol && (chan->protocol != skb->protocol)){
+ printk(KERN_INFO
+ "%s: unsupported Ethertype 0x%04X on interface %s!\n",
+ chan->name, htons(skb->protocol), dev->name);
+
+ printk(KERN_INFO "PROTO %Xn", htons(chan->protocol));
+ ++chan->ifstats.tx_errors;
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ ++chan->if_send_stat.if_send_protocol_error;
+
+ }else switch (chan->common.state){
+
+ case WAN_DISCONNECTED:
+ /* Try to establish connection. If succeded, then start
+ * transmission, else drop a packet.
+ */
+ if (chan->common.usedby == API){
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ break;
+ }else{
+ if (chan_connect(dev) != 0){
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ break;
+ }
+ }
+ /* fall through */
+
+ case WAN_CONNECTED:
+ if( skb->protocol == htons(ETH_P_IPX)) {
+ if(chan->enable_IPX) {
+ switch_net_numbers( skb->data,
+ chan->network_number, 0);
+ } else {
+ ++card->wandev.stats.tx_dropped;
+ ++chan->ifstats.tx_dropped;
+ ++chan->if_send_stat.if_send_protocol_error;
+ goto if_send_crit_exit;
+ }
+ }
+ /* We never drop here, if cannot send than, copy
+ * a packet into a transmit buffer
+ */
+ chan_send(dev, skb->data, skb->len, 0);
+ break;
+
+ default:
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ break;
+ }
+
+
+if_send_crit_exit:
+
+ dev_kfree_skb_any(skb);
+
+ netif_start_queue(dev);
+ clear_bit(SEND_CRIT,(void*)&card->wandev.critical);
+ S508_S514_unlock(card, &smp_flags);
+ return 0;
+}
+
+/*============================================================================
+ * Setup so that a frame can be transmitted on the occurrence of a transmit
+ * interrupt.
+ *===========================================================================*/
+
+static void setup_for_delayed_transmit(struct net_device* dev, void* buf,
+ unsigned len)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ TX25Status* status = card->flags;
+
+ ++chan->if_send_stat.if_send_adptr_bfrs_full;
+
+ if(chan->transmit_length) {
+ printk(KERN_INFO "%s: Error, transmit length set in delayed transmit!\n",
+ card->devname);
+ return;
+ }
+
+ if (chan->common.usedby == API){
+ if (len > X25_CHAN_MTU+sizeof(x25api_hdr_t)) {
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ printk(KERN_INFO "%s: Length is too big for delayed transmit\n",
+ card->devname);
+ return;
+ }
+ }else{
+ if (len > X25_MAX_DATA) {
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ printk(KERN_INFO "%s: Length is too big for delayed transmit\n",
+ card->devname);
+ return;
+ }
+ }
+
+ chan->transmit_length = len;
+ atomic_set(&chan->common.driver_busy,1);
+ memcpy(chan->transmit_buffer, buf, len);
+
+ ++chan->if_send_stat.if_send_tx_int_enabled;
+
+ /* Enable Transmit Interrupt */
+ ++card->u.x.tx_interrupts_pending;
+ status->imask |= INTR_ON_TX_FRAME;
+}
+
+
+/*===============================================================
+ * net_device_stats
+ *
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ *
+ *==============================================================*/
+static struct net_device_stats *if_stats(struct net_device* dev)
+{
+ x25_channel_t *chan = dev->priv;
+
+ if(chan == NULL)
+ return NULL;
+
+ return &chan->ifstats;
+}
+
+
+/*
+ * Interrupt Handlers
+ */
+
+/*
+ * X.25 Interrupt Service Routine.
+ */
+
+static void wpx_isr (sdla_t* card)
+{
+ TX25Status* status = card->flags;
+
+ card->in_isr = 1;
+ ++card->statistics.isr_entry;
+
+ if (test_bit(PERI_CRIT,(void*)&card->wandev.critical)){
+ card->in_isr=0;
+ status->iflags = 0;
+ return;
+ }
+
+ if (test_bit(SEND_CRIT, (void*)&card->wandev.critical)){
+
+ printk(KERN_INFO "%s: wpx_isr: wandev.critical set to 0x%02lx, int type = 0x%02x\n",
+ card->devname, card->wandev.critical, status->iflags);
+ card->in_isr = 0;
+ status->iflags = 0;
+ return;
+ }
+
+ /* For all interrupts set the critical flag to CRITICAL_RX_INTR.
+ * If the if_send routine is called with this flag set it will set
+ * the enable transmit flag to 1. (for a delayed interrupt)
+ */
+ switch (status->iflags){
+
+ case RX_INTR_PENDING: /* receive interrupt */
+ rx_intr(card);
+ break;
+
+ case TX_INTR_PENDING: /* transmit interrupt */
+ tx_intr(card);
+ break;
+
+ case MODEM_INTR_PENDING: /* modem status interrupt */
+ status_intr(card);
+ break;
+
+ case X25_ASY_TRANS_INTR_PENDING: /* network event interrupt */
+ event_intr(card);
+ break;
+
+ case TIMER_INTR_PENDING:
+ timer_intr(card);
+ break;
+
+ default: /* unwanted interrupt */
+ spur_intr(card);
+ }
+
+ card->in_isr = 0;
+ status->iflags = 0; /* clear interrupt condition */
+}
+
+/*
+ * Receive interrupt handler.
+ * This routine handles fragmented IP packets using M-bit according to the
+ * RFC1356.
+ * o map ligical channel number to network interface.
+ * o allocate socket buffer or append received packet to the existing one.
+ * o if M-bit is reset (i.e. it's the last packet in a sequence) then
+ * decapsulate packet and pass socket buffer to the protocol stack.
+ *
+ * Notes:
+ * 1. When allocating a socket buffer, if M-bit is set then more data is
+ * coming and we have to allocate buffer for the maximum IP packet size
+ * expected on this channel.
+ * 2. If something goes wrong and X.25 packet has to be dropped (e.g. no
+ * socket buffers available) the whole packet sequence must be discarded.
+ */
+
+static void rx_intr (sdla_t* card)
+{
+ TX25Mbox* rxmb = card->rxmb;
+ unsigned lcn = rxmb->cmd.lcn;
+ struct net_device* dev = find_channel(card,lcn);
+ x25_channel_t* chan;
+ struct sk_buff* skb=NULL;
+
+ if (dev == NULL){
+ /* Invalid channel, discard packet */
+ printk(KERN_INFO "%s: receiving on orphaned LCN %d!\n",
+ card->devname, lcn);
+ return;
+ }
+
+ chan = dev->priv;
+ chan->i_timeout_sofar = jiffies;
+
+
+ /* Copy the data from the board, into an
+ * skb buffer
+ */
+ if (wanpipe_pull_data_in_skb(card,dev,&skb)){
+ ++chan->ifstats.rx_dropped;
+ ++card->wandev.stats.rx_dropped;
+ ++chan->rx_intr_stat.rx_intr_no_socket;
+ ++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+ return;
+ }
+
+ dev->last_rx = jiffies; /* timestamp */
+
+
+ /* ------------ API ----------------*/
+
+ if (chan->common.usedby == API){
+
+ if (bh_enqueue(dev, skb)){
+ ++chan->ifstats.rx_dropped;
+ ++card->wandev.stats.rx_dropped;
+ ++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ ++chan->ifstats.rx_packets;
+ chan->ifstats.rx_bytes += skb->len;
+
+
+ chan->rx_skb = NULL;
+ if (!test_and_set_bit(0, &chan->tq_working)){
+ wanpipe_queue_work(&chan->common.wanpipe_work);
+ }
+ return;
+ }
+
+
+ /* ------------- WANPIPE -------------------*/
+
+ /* set rx_skb to NULL so we won't access it later when kernel already owns it */
+ chan->rx_skb=NULL;
+
+ /* Decapsulate packet, if necessary */
+ if (!skb->protocol && !wanrouter_type_trans(skb, dev)){
+ /* can't decapsulate packet */
+ dev_kfree_skb_any(skb);
+ ++chan->ifstats.rx_errors;
+ ++chan->ifstats.rx_dropped;
+ ++card->wandev.stats.rx_dropped;
+ ++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+
+ }else{
+ if( handle_IPXWAN(skb->data, chan->name,
+ chan->enable_IPX, chan->network_number,
+ skb->protocol)){
+
+ if( chan->enable_IPX ){
+ if(chan_send(dev, skb->data, skb->len,0)){
+ chan->tx_skb = skb;
+ }else{
+ dev_kfree_skb_any(skb);
+ ++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+ }
+ }else{
+ /* increment IPX packet dropped statistic */
+ ++chan->ifstats.rx_dropped;
+ ++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+ }
+ }else{
+ skb->mac.raw = skb->data;
+ chan->ifstats.rx_bytes += skb->len;
+ ++chan->ifstats.rx_packets;
+ ++chan->rx_intr_stat.rx_intr_bfr_passed_to_stack;
+ netif_rx(skb);
+ }
+ }
+
+ return;
+}
+
+
+static int wanpipe_pull_data_in_skb(sdla_t *card, struct net_device *dev,
+ struct sk_buff **skb)
+{
+ void *bufptr;
+ TX25Mbox* rxmb = card->rxmb;
+ unsigned len = rxmb->cmd.length; /* packet length */
+ unsigned qdm = rxmb->cmd.qdm; /* Q,D and M bits */
+ x25_channel_t *chan = dev->priv;
+ struct sk_buff *new_skb = *skb;
+
+ if (chan->common.usedby == WANPIPE){
+ if (chan->drop_sequence){
+ if (!(qdm & 0x01)){
+ chan->drop_sequence = 0;
+ }
+ return 1;
+ }
+ new_skb = chan->rx_skb;
+ }else{
+ /* Add on the API header to the received
+ * data
+ */
+ len += sizeof(x25api_hdr_t);
+ }
+
+ if (new_skb == NULL){
+ int bufsize;
+
+ if (chan->common.usedby == WANPIPE){
+ bufsize = (qdm & 0x01) ? dev->mtu : len;
+ }else{
+ bufsize = len;
+ }
+
+ /* Allocate new socket buffer */
+ new_skb = dev_alloc_skb(bufsize + dev->hard_header_len);
+ if (new_skb == NULL){
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ chan->drop_sequence = 1; /* set flag */
+ ++chan->ifstats.rx_dropped;
+ return 1;
+ }
+ }
+
+ if (skb_tailroom(new_skb) < len){
+ /* No room for the packet. Call off the whole thing! */
+ dev_kfree_skb_any(new_skb);
+ if (chan->common.usedby == WANPIPE){
+ chan->rx_skb = NULL;
+ if (qdm & 0x01){
+ chan->drop_sequence = 1;
+ }
+ }
+
+ printk(KERN_INFO "%s: unexpectedly long packet sequence "
+ "on interface %s!\n", card->devname, dev->name);
+ ++chan->ifstats.rx_length_errors;
+ return 1;
+ }
+
+ bufptr = skb_put(new_skb,len);
+
+
+ if (chan->common.usedby == API){
+ /* Fill in the x25api header
+ */
+ x25api_t * api_data = (x25api_t*)bufptr;
+ api_data->hdr.qdm = rxmb->cmd.qdm;
+ api_data->hdr.cause = rxmb->cmd.cause;
+ api_data->hdr.diagn = rxmb->cmd.diagn;
+ api_data->hdr.length = rxmb->cmd.length;
+ memcpy(api_data->data, rxmb->data, rxmb->cmd.length);
+ }else{
+ memcpy(bufptr, rxmb->data, len);
+ }
+
+ new_skb->dev = dev;
+
+ if (chan->common.usedby == API){
+ new_skb->mac.raw = new_skb->data;
+ new_skb->protocol = htons(X25_PROT);
+ new_skb->pkt_type = WAN_PACKET_DATA;
+ }else{
+ new_skb->protocol = chan->protocol;
+ chan->rx_skb = new_skb;
+ }
+
+ /* If qdm bit is set, more data is coming
+ * thus, exit and wait for more data before
+ * sending the packet up. (Used by router only)
+ */
+ if ((qdm & 0x01) && (chan->common.usedby == WANPIPE))
+ return 1;
+
+ *skb = new_skb;
+
+ return 0;
+}
+
+/*===============================================================
+ * tx_intr
+ *
+ * Transmit interrupt handler.
+ * For each dev, check that there is something to send.
+ * If data available, transmit.
+ *
+ *===============================================================*/
+
+static void tx_intr (sdla_t* card)
+{
+ struct net_device *dev;
+ TX25Status* status = card->flags;
+ unsigned char more_to_tx=0;
+ x25_channel_t *chan=NULL;
+ int i=0;
+
+ if (card->u.x.tx_dev == NULL){
+ card->u.x.tx_dev = card->wandev.dev;
+ }
+
+ dev = card->u.x.tx_dev;
+
+ for (;;){
+
+ chan = dev->priv;
+ if (chan->transmit_length){
+ /* Device was set to transmit, check if the TX
+ * buffers are available
+ */
+ if (chan->common.state != WAN_CONNECTED){
+ chan->transmit_length = 0;
+ atomic_set(&chan->common.driver_busy,0);
+ chan->tx_offset=0;
+ if (netif_queue_stopped(dev)){
+ if (chan->common.usedby == API){
+ netif_start_queue(dev);
+ wakeup_sk_bh(dev);
+ }else{
+ netif_wake_queue(dev);
+ }
+ }
+ dev = move_dev_to_next(card,dev);
+ break;
+ }
+
+ if ((status->cflags[chan->ch_idx] & 0x40 || card->u.x.LAPB_hdlc) &&
+ (*card->u.x.hdlc_buf_status & 0x40) ){
+ /* Tx buffer available, we can send */
+
+ if (tx_intr_send(card, dev)){
+ more_to_tx=1;
+ }
+
+ /* If more than one interface present, move the
+ * device pointer to the next interface, so on the
+ * next TX interrupt we will try sending from it.
+ */
+ dev = move_dev_to_next(card,dev);
+ break;
+ }else{
+ /* Tx buffers not available, but device set
+ * the TX interrupt. Set more_to_tx and try
+ * to transmit for other devices.
+ */
+ more_to_tx=1;
+ dev = move_dev_to_next(card,dev);
+ }
+
+ }else{
+ /* This device was not set to transmit,
+ * go to next
+ */
+ dev = move_dev_to_next(card,dev);
+ }
+
+ if (++i == card->u.x.no_dev){
+ if (!more_to_tx){
+ DBG_PRINTK(KERN_INFO "%s: Nothing to Send in TX INTR\n",
+ card->devname);
+ }
+ break;
+ }
+
+ } //End of FOR
+
+ card->u.x.tx_dev = dev;
+
+ if (!more_to_tx){
+ /* if any other interfaces have transmit interrupts pending, */
+ /* do not disable the global transmit interrupt */
+ if (!(--card->u.x.tx_interrupts_pending)){
+ status->imask &= ~INTR_ON_TX_FRAME;
+ }
+ }
+ return;
+}
+
+/*===============================================================
+ * move_dev_to_next
+ *
+ *
+ *===============================================================*/
+
+
+struct net_device *move_dev_to_next(sdla_t *card, struct net_device *dev)
+{
+ if (card->u.x.no_dev != 1){
+ if (!*((struct net_device **)dev->priv))
+ return card->wandev.dev;
+ else
+ return *((struct net_device **)dev->priv);
+ }
+ return dev;
+}
+
+/*===============================================================
+ * tx_intr_send
+ *
+ *
+ *===============================================================*/
+
+static int tx_intr_send(sdla_t *card, struct net_device *dev)
+{
+ x25_channel_t* chan = dev->priv;
+
+ if (chan_send (dev,chan->transmit_buffer,chan->transmit_length,1)){
+
+ /* Packet was split up due to its size, do not disable
+ * tx_intr
+ */
+ return 1;
+ }
+
+ chan->transmit_length=0;
+ atomic_set(&chan->common.driver_busy,0);
+ chan->tx_offset=0;
+
+ /* If we are in API mode, wakeup the
+ * sock BH handler, not the NET_BH */
+ if (netif_queue_stopped(dev)){
+ if (chan->common.usedby == API){
+ netif_start_queue(dev);
+ wakeup_sk_bh(dev);
+ }else{
+ netif_wake_queue(dev);
+ }
+ }
+ return 0;
+}
+
+
+/*===============================================================
+ * timer_intr
+ *
+ * Timer interrupt handler.
+ * Check who called the timer interrupt and perform
+ * action accordingly.
+ *
+ *===============================================================*/
+
+static void timer_intr (sdla_t *card)
+{
+ TX25Status* status = card->flags;
+
+ if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC){
+
+ if (timer_intr_cmd_exec(card) == 0){
+ card->u.x.timer_int_enabled &=
+ ~TMR_INT_ENABLED_CMD_EXEC;
+ }
+
+ }else if(card->u.x.timer_int_enabled & TMR_INT_ENABLED_UDP_PKT) {
+
+ if ((*card->u.x.hdlc_buf_status & 0x40) &&
+ card->u.x.udp_type == UDP_XPIPE_TYPE){
+
+ if(process_udp_mgmt_pkt(card)) {
+ card->u.x.timer_int_enabled &=
+ ~TMR_INT_ENABLED_UDP_PKT;
+ }
+ }
+
+ }else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_ACTIVE) {
+
+ struct net_device *dev = card->u.x.poll_device;
+ x25_channel_t *chan = NULL;
+
+ if (!dev){
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_ACTIVE;
+ return;
+ }
+ chan = dev->priv;
+
+ printk(KERN_INFO
+ "%s: Closing down Idle link %s on LCN %d\n",
+ card->devname,chan->name,chan->common.lcn);
+ chan->i_timeout_sofar = jiffies;
+ chan_disc(dev);
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_ACTIVE;
+ card->u.x.poll_device=NULL;
+
+ }else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_CONNECT_ON) {
+
+ wanpipe_set_state(card, WAN_CONNECTED);
+ if (card->u.x.LAPB_hdlc){
+ struct net_device *dev = card->wandev.dev;
+ set_chan_state(dev,WAN_CONNECTED);
+ send_delayed_cmd_result(card,dev,card->mbox);
+ }
+
+ /* 0x8F enable all interrupts */
+ x25_set_intr_mode(card, INTR_ON_RX_FRAME|
+ INTR_ON_TX_FRAME|
+ INTR_ON_MODEM_STATUS_CHANGE|
+ //INTR_ON_COMMAND_COMPLETE|
+ X25_ASY_TRANS_INTR_PENDING |
+ INTR_ON_TIMER |
+ DIRECT_RX_INTR_USAGE
+ );
+
+ status->imask &= ~INTR_ON_TX_FRAME; /* mask Tx interrupts */
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_CONNECT_ON;
+
+ }else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_CONNECT_OFF) {
+
+ //printk(KERN_INFO "Poll connect, Turning OFF\n");
+ disconnect(card);
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_CONNECT_OFF;
+
+ }else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_POLL_DISCONNECT) {
+
+ //printk(KERN_INFO "POll disconnect, trying to connect\n");
+ connect(card);
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_POLL_DISCONNECT;
+
+ }else if (card->u.x.timer_int_enabled & TMR_INT_ENABLED_UPDATE){
+
+ if (*card->u.x.hdlc_buf_status & 0x40){
+ x25_get_err_stats(card);
+ x25_get_stats(card);
+ card->u.x.timer_int_enabled &= ~TMR_INT_ENABLED_UPDATE;
+ }
+ }
+
+ if(!card->u.x.timer_int_enabled){
+ //printk(KERN_INFO "Turning Timer Off \n");
+ status->imask &= ~INTR_ON_TIMER;
+ }
+}
+
+/*====================================================================
+ * Modem status interrupt handler.
+ *===================================================================*/
+static void status_intr (sdla_t* card)
+{
+
+ /* Added to avoid Modem status message flooding */
+ static TX25ModemStatus last_stat;
+
+ TX25Mbox* mbox = card->mbox;
+ TX25ModemStatus *modem_status;
+ struct net_device *dev;
+ x25_channel_t *chan;
+ int err;
+
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_READ_MODEM_STATUS;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err){
+ x25_error(card, err, X25_READ_MODEM_STATUS, 0);
+ }else{
+
+ modem_status = (TX25ModemStatus*)mbox->data;
+
+ /* Check if the last status was the same
+ * if it was, do NOT print message again */
+
+ if (last_stat.status != modem_status->status){
+
+ printk(KERN_INFO "%s: Modem Status Change: DCD=%s, CTS=%s\n",
+ card->devname,DCD(modem_status->status),CTS(modem_status->status));
+
+ last_stat.status = modem_status->status;
+
+ if (card->u.x.oob_on_modem){
+
+ mbox->cmd.pktType = mbox->cmd.command;
+ mbox->cmd.result = 0x08;
+
+ /* Send a OOB to all connected sockets */
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device**)dev->priv)) {
+ chan=dev->priv;
+ if (chan->common.usedby == API){
+ send_oob_msg(card,dev,mbox);
+ }
+ }
+
+ /* The modem OOB message will probably kill the
+ * the link. If we don't clear the flag here,
+ * a deadlock could occur */
+ if (atomic_read(&card->u.x.command_busy)){
+ atomic_set(&card->u.x.command_busy,0);
+ }
+ }
+ }
+ }
+
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_HDLC_LINK_STATUS;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err){
+ x25_error(card, err, X25_HDLC_LINK_STATUS, 0);
+ }
+
+}
+
+/*====================================================================
+ * Network event interrupt handler.
+ *===================================================================*/
+static void event_intr (sdla_t* card)
+{
+ x25_fetch_events(card);
+}
+
+/*====================================================================
+ * Spurious interrupt handler.
+ * o print a warning
+ * o
+ *====================================================================*/
+
+static void spur_intr (sdla_t* card)
+{
+ printk(KERN_INFO "%s: spurious interrupt!\n", card->devname);
+}
+
+
+/*
+ * Background Polling Routines
+ */
+
+/*====================================================================
+ * Main polling routine.
+ * This routine is repeatedly called by the WANPIPE 'thread' to allow for
+ * time-dependent housekeeping work.
+ *
+ * Notes:
+ * 1. This routine may be called on interrupt context with all interrupts
+ * enabled. Beware!
+ *====================================================================*/
+
+static void wpx_poll (sdla_t *card)
+{
+ if (!card->wandev.dev){
+ goto wpx_poll_exit;
+ }
+
+ if (card->open_cnt != card->u.x.num_of_ch){
+ goto wpx_poll_exit;
+ }
+
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ goto wpx_poll_exit;
+ }
+
+ if (test_bit(SEND_CRIT,&card->wandev.critical)){
+ goto wpx_poll_exit;
+ }
+
+ switch(card->wandev.state){
+ case WAN_CONNECTED:
+ poll_active(card);
+ break;
+
+ case WAN_CONNECTING:
+ poll_connecting(card);
+ break;
+
+ case WAN_DISCONNECTED:
+ poll_disconnected(card);
+ break;
+ }
+
+wpx_poll_exit:
+ clear_bit(POLL_CRIT,&card->wandev.critical);
+ return;
+}
+
+static void trigger_x25_poll(sdla_t *card)
+{
+ schedule_work(&card->u.x.x25_poll_work);
+}
+
+/*====================================================================
+ * Handle physical link establishment phase.
+ * o if connection timed out, disconnect the link.
+ *===================================================================*/
+
+static void poll_connecting (sdla_t* card)
+{
+ volatile TX25Status* status = card->flags;
+
+ if (status->gflags & X25_HDLC_ABM){
+
+ timer_intr_exec (card, TMR_INT_ENABLED_POLL_CONNECT_ON);
+
+ }else if ((jiffies - card->state_tick) > CONNECT_TIMEOUT){
+
+ timer_intr_exec (card, TMR_INT_ENABLED_POLL_CONNECT_OFF);
+
+ }
+}
+
+/*====================================================================
+ * Handle physical link disconnected phase.
+ * o if hold-down timeout has expired and there are open interfaces,
+ * connect link.
+ *===================================================================*/
+
+static void poll_disconnected (sdla_t* card)
+{
+ struct net_device *dev;
+ x25_channel_t *chan;
+ TX25Status* status = card->flags;
+
+ if (!card->u.x.LAPB_hdlc && card->open_cnt &&
+ ((jiffies - card->state_tick) > HOLD_DOWN_TIME)){
+ timer_intr_exec(card, TMR_INT_ENABLED_POLL_DISCONNECT);
+ }
+
+
+ if ((dev=card->wandev.dev) == NULL)
+ return;
+
+ if ((chan=dev->priv) == NULL)
+ return;
+
+ if (chan->common.usedby == API &&
+ atomic_read(&chan->common.command) &&
+ card->u.x.LAPB_hdlc){
+
+ if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC))
+ card->u.x.timer_int_enabled |= TMR_INT_ENABLED_CMD_EXEC;
+
+ if (!(status->imask & INTR_ON_TIMER))
+ status->imask |= INTR_ON_TIMER;
+ }
+
+}
+
+/*====================================================================
+ * Handle active link phase.
+ * o fetch X.25 asynchronous events.
+ * o kick off transmission on all interfaces.
+ *===================================================================*/
+
+static void poll_active (sdla_t* card)
+{
+ struct net_device* dev;
+ TX25Status* status = card->flags;
+
+ for (dev = card->wandev.dev; dev;
+ dev = *((struct net_device **)dev->priv)){
+ x25_channel_t* chan = dev->priv;
+
+ /* If SVC has been idle long enough, close virtual circuit */
+ if ( chan->common.svc &&
+ chan->common.state == WAN_CONNECTED &&
+ chan->common.usedby == WANPIPE ){
+
+ if( (jiffies - chan->i_timeout_sofar) / HZ > chan->idle_timeout ){
+ /* Close svc */
+ card->u.x.poll_device=dev;
+ timer_intr_exec (card, TMR_INT_ENABLED_POLL_ACTIVE);
+ }
+ }
+
+#ifdef PRINT_DEBUG
+ chan->ifstats.tx_compressed = atomic_read(&chan->common.command);
+ chan->ifstats.tx_errors = chan->common.state;
+ chan->ifstats.rx_fifo_errors = atomic_read(&card->u.x.command_busy);
+ ++chan->ifstats.tx_bytes;
+
+ chan->ifstats.rx_fifo_errors=atomic_read(&chan->common.disconnect);
+ chan->ifstats.multicast=atomic_read(&chan->bh_buff_used);
+ chan->ifstats.rx_length_errors=*card->u.x.hdlc_buf_status;
+#endif
+
+ if (chan->common.usedby == API &&
+ atomic_read(&chan->common.command) &&
+ !card->u.x.LAPB_hdlc){
+
+ if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC))
+ card->u.x.timer_int_enabled |= TMR_INT_ENABLED_CMD_EXEC;
+
+ if (!(status->imask & INTR_ON_TIMER))
+ status->imask |= INTR_ON_TIMER;
+ }
+
+ if ((chan->common.usedby == API) &&
+ atomic_read(&chan->common.disconnect)){
+
+ if (chan->common.state == WAN_DISCONNECTED){
+ atomic_set(&chan->common.disconnect,0);
+ return;
+ }
+
+ atomic_set(&chan->common.command,X25_CLEAR_CALL);
+ if (!(card->u.x.timer_int_enabled & TMR_INT_ENABLED_CMD_EXEC))
+ card->u.x.timer_int_enabled |= TMR_INT_ENABLED_CMD_EXEC;
+
+ if (!(status->imask & INTR_ON_TIMER))
+ status->imask |= INTR_ON_TIMER;
+ }
+ }
+}
+
+static void timer_intr_exec(sdla_t *card, unsigned char TYPE)
+{
+ TX25Status* status = card->flags;
+ card->u.x.timer_int_enabled |= TYPE;
+ if (!(status->imask & INTR_ON_TIMER))
+ status->imask |= INTR_ON_TIMER;
+}
+
+
+/*====================================================================
+ * SDLA Firmware-Specific Functions
+ *
+ * Almost all X.25 commands can unexpetedly fail due to so called 'X.25
+ * asynchronous events' such as restart, interrupt, incoming call request,
+ * call clear request, etc. They can't be ignored and have to be delt with
+ * immediately. To tackle with this problem we execute each interface
+ * command in a loop until good return code is received or maximum number
+ * of retries is reached. Each interface command returns non-zero return
+ * code, an asynchronous event/error handler x25_error() is called.
+ *====================================================================*/
+
+/*====================================================================
+ * Read X.25 firmware version.
+ * Put code version as ASCII string in str.
+ *===================================================================*/
+
+static int x25_get_version (sdla_t* card, char* str)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_READ_CODE_VERSION;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- &&
+ x25_error(card, err, X25_READ_CODE_VERSION, 0));
+
+ if (!err && str)
+ {
+ int len = mbox->cmd.length;
+
+ memcpy(str, mbox->data, len);
+ str[len] = '\0';
+ }
+ return err;
+}
+
+/*====================================================================
+ * Configure adapter.
+ *===================================================================*/
+
+static int x25_configure (sdla_t* card, TX25Config* conf)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do{
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ memcpy(mbox->data, (void*)conf, sizeof(TX25Config));
+ mbox->cmd.length = sizeof(TX25Config);
+ mbox->cmd.command = X25_SET_CONFIGURATION;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_SET_CONFIGURATION, 0));
+ return err;
+}
+
+/*====================================================================
+ * Configure adapter for HDLC only.
+ *===================================================================*/
+
+static int hdlc_configure (sdla_t* card, TX25Config* conf)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do{
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ memcpy(mbox->data, (void*)conf, sizeof(TX25Config));
+ mbox->cmd.length = sizeof(TX25Config);
+ mbox->cmd.command = X25_HDLC_SET_CONFIG;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_SET_CONFIGURATION, 0));
+
+ return err;
+}
+
+static int set_hdlc_level (sdla_t* card)
+{
+
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do{
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = SET_PROTOCOL_LEVEL;
+ mbox->cmd.length = 1;
+ mbox->data[0] = HDLC_LEVEL; //| DO_HDLC_LEVEL_ERROR_CHECKING;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, SET_PROTOCOL_LEVEL, 0));
+
+ return err;
+}
+
+
+
+/*====================================================================
+ * Get communications error statistics.
+ *====================================================================*/
+
+static int x25_get_err_stats (sdla_t* card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_HDLC_READ_COMM_ERR;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_HDLC_READ_COMM_ERR, 0));
+
+ if (!err)
+ {
+ THdlcCommErr* stats = (void*)mbox->data;
+
+ card->wandev.stats.rx_over_errors = stats->rxOverrun;
+ card->wandev.stats.rx_crc_errors = stats->rxBadCrc;
+ card->wandev.stats.rx_missed_errors = stats->rxAborted;
+ card->wandev.stats.tx_aborted_errors = stats->txAborted;
+ }
+ return err;
+}
+
+/*====================================================================
+ * Get protocol statistics.
+ *===================================================================*/
+
+static int x25_get_stats (sdla_t* card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_READ_STATISTICS;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_READ_STATISTICS, 0)) ;
+
+ if (!err)
+ {
+ TX25Stats* stats = (void*)mbox->data;
+
+ card->wandev.stats.rx_packets = stats->rxData;
+ card->wandev.stats.tx_packets = stats->txData;
+ }
+ return err;
+}
+
+/*====================================================================
+ * Close HDLC link.
+ *===================================================================*/
+
+static int x25_close_hdlc (sdla_t* card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_HDLC_LINK_CLOSE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_CLOSE, 0));
+
+ return err;
+}
+
+
+/*====================================================================
+ * Open HDLC link.
+ *===================================================================*/
+
+static int x25_open_hdlc (sdla_t* card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_HDLC_LINK_OPEN;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_OPEN, 0));
+
+ return err;
+}
+
+/*=====================================================================
+ * Setup HDLC link.
+ *====================================================================*/
+static int x25_setup_hdlc (sdla_t* card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_HDLC_LINK_SETUP;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_SETUP, 0));
+
+ return err;
+}
+
+/*====================================================================
+ * Set (raise/drop) DTR.
+ *===================================================================*/
+
+static int x25_set_dtr (sdla_t* card, int dtr)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->data[0] = 0;
+ mbox->data[2] = 0;
+ mbox->data[1] = dtr ? 0x02 : 0x01;
+ mbox->cmd.length = 3;
+ mbox->cmd.command = X25_SET_GLOBAL_VARS;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_SET_GLOBAL_VARS, 0));
+
+ return err;
+}
+
+/*====================================================================
+ * Set interrupt mode.
+ *===================================================================*/
+
+static int x25_set_intr_mode (sdla_t* card, int mode)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->data[0] = mode;
+ if (card->hw.fwid == SFID_X25_508){
+ mbox->data[1] = card->hw.irq;
+ mbox->data[2] = 2;
+ mbox->cmd.length = 3;
+ }else {
+ mbox->cmd.length = 1;
+ }
+ mbox->cmd.command = X25_SET_INTERRUPT_MODE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_SET_INTERRUPT_MODE, 0));
+
+ return err;
+}
+
+/*====================================================================
+ * Read X.25 channel configuration.
+ *===================================================================*/
+
+static int x25_get_chan_conf (sdla_t* card, x25_channel_t* chan)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int lcn = chan->common.lcn;
+ int err;
+
+ do{
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.lcn = lcn;
+ mbox->cmd.command = X25_READ_CHANNEL_CONFIG;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_READ_CHANNEL_CONFIG, lcn));
+
+ if (!err)
+ {
+ TX25Status* status = card->flags;
+
+ /* calculate an offset into the array of status bytes */
+ if (card->u.x.hi_svc <= X25_MAX_CHAN){
+
+ chan->ch_idx = lcn - 1;
+
+ }else{
+ int offset;
+
+ /* FIX: Apr 14 2000 : Nenad Corbic
+ * The data field was being compared to 0x1F using
+ * '&&' instead of '&'.
+ * This caused X25API to fail for LCNs greater than 255.
+ */
+ switch (mbox->data[0] & 0x1F)
+ {
+ case 0x01:
+ offset = status->pvc_map; break;
+ case 0x03:
+ offset = status->icc_map; break;
+ case 0x07:
+ offset = status->twc_map; break;
+ case 0x0B:
+ offset = status->ogc_map; break;
+ default:
+ offset = 0;
+ }
+ chan->ch_idx = lcn - 1 - offset;
+ }
+
+ /* get actual transmit packet size on this channel */
+ switch(mbox->data[1] & 0x38)
+ {
+ case 0x00:
+ chan->tx_pkt_size = 16;
+ break;
+ case 0x08:
+ chan->tx_pkt_size = 32;
+ break;
+ case 0x10:
+ chan->tx_pkt_size = 64;
+ break;
+ case 0x18:
+ chan->tx_pkt_size = 128;
+ break;
+ case 0x20:
+ chan->tx_pkt_size = 256;
+ break;
+ case 0x28:
+ chan->tx_pkt_size = 512;
+ break;
+ case 0x30:
+ chan->tx_pkt_size = 1024;
+ break;
+ }
+ if (card->u.x.logging)
+ printk(KERN_INFO "%s: X.25 packet size on LCN %d is %d.\n",
+ card->devname, lcn, chan->tx_pkt_size);
+ }
+ return err;
+}
+
+/*====================================================================
+ * Place X.25 call.
+ *====================================================================*/
+
+static int x25_place_call (sdla_t* card, x25_channel_t* chan)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+ char str[64];
+
+
+ if (chan->protocol == htons(ETH_P_IP)){
+ sprintf(str, "-d%s -uCC", chan->addr);
+
+ }else if (chan->protocol == htons(ETH_P_IPX)){
+ sprintf(str, "-d%s -u800000008137", chan->addr);
+
+ }
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ strcpy(mbox->data, str);
+ mbox->cmd.length = strlen(str);
+ mbox->cmd.command = X25_PLACE_CALL;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_PLACE_CALL, 0));
+
+ if (!err){
+ bind_lcn_to_dev (card, chan->dev, mbox->cmd.lcn);
+ }
+ return err;
+}
+
+/*====================================================================
+ * Accept X.25 call.
+ *====================================================================*/
+
+static int x25_accept_call (sdla_t* card, int lcn, int qdm)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.lcn = lcn;
+ mbox->cmd.qdm = qdm;
+ mbox->cmd.command = X25_ACCEPT_CALL;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_ACCEPT_CALL, lcn));
+
+ return err;
+}
+
+/*====================================================================
+ * Clear X.25 call.
+ *====================================================================*/
+
+static int x25_clear_call (sdla_t* card, int lcn, int cause, int diagn)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.lcn = lcn;
+ mbox->cmd.cause = cause;
+ mbox->cmd.diagn = diagn;
+ mbox->cmd.command = X25_CLEAR_CALL;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, X25_CLEAR_CALL, lcn));
+
+ return err;
+}
+
+/*====================================================================
+ * Send X.25 data packet.
+ *====================================================================*/
+
+static int x25_send (sdla_t* card, int lcn, int qdm, int len, void* buf)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = MAX_CMD_RETRY;
+ int err;
+ unsigned char cmd;
+
+ if (card->u.x.LAPB_hdlc)
+ cmd = X25_HDLC_WRITE;
+ else
+ cmd = X25_WRITE;
+
+ do
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ memcpy(mbox->data, buf, len);
+ mbox->cmd.length = len;
+ mbox->cmd.lcn = lcn;
+
+ if (card->u.x.LAPB_hdlc){
+ mbox->cmd.pf = qdm;
+ }else{
+ mbox->cmd.qdm = qdm;
+ }
+
+ mbox->cmd.command = cmd;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && retry-- && x25_error(card, err, cmd , lcn));
+
+
+ /* If buffers are busy the return code for LAPB HDLC is
+ * 1. The above functions are looking for return code
+ * of X25RES_NOT_READY if busy. */
+
+ if (card->u.x.LAPB_hdlc && err == 1){
+ err = X25RES_NOT_READY;
+ }
+
+ return err;
+}
+
+/*====================================================================
+ * Fetch X.25 asynchronous events.
+ *===================================================================*/
+
+static int x25_fetch_events (sdla_t* card)
+{
+ TX25Status* status = card->flags;
+ TX25Mbox* mbox = card->mbox;
+ int err = 0;
+
+ if (status->gflags & 0x20)
+ {
+ memset(&mbox->cmd, 0, sizeof(TX25Cmd));
+ mbox->cmd.command = X25_IS_DATA_AVAILABLE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err) x25_error(card, err, X25_IS_DATA_AVAILABLE, 0);
+ }
+ return err;
+}
+
+/*====================================================================
+ * X.25 asynchronous event/error handler.
+ * This routine is called each time interface command returns
+ * non-zero return code to handle X.25 asynchronous events and
+ * common errors. Return non-zero to repeat command or zero to
+ * cancel it.
+ *
+ * Notes:
+ * 1. This function may be called recursively, as handling some of the
+ * asynchronous events (e.g. call request) requires execution of the
+ * interface command(s) that, in turn, may also return asynchronous
+ * events. To avoid re-entrancy problems we copy mailbox to dynamically
+ * allocated memory before processing events.
+ *====================================================================*/
+
+static int x25_error (sdla_t* card, int err, int cmd, int lcn)
+{
+ int retry = 1;
+ unsigned dlen = ((TX25Mbox*)card->mbox)->cmd.length;
+ TX25Mbox* mb;
+
+ mb = kmalloc(sizeof(TX25Mbox) + dlen, GFP_ATOMIC);
+ if (mb == NULL)
+ {
+ printk(KERN_ERR "%s: x25_error() out of memory!\n",
+ card->devname);
+ return 0;
+ }
+ memcpy(mb, card->mbox, sizeof(TX25Mbox) + dlen);
+ switch (err){
+
+ case X25RES_ASYNC_PACKET: /* X.25 asynchronous packet was received */
+
+ mb->data[dlen] = '\0';
+
+ switch (mb->cmd.pktType & 0x7F){
+
+ case ASE_CALL_RQST: /* incoming call */
+ retry = incoming_call(card, cmd, lcn, mb);
+ break;
+
+ case ASE_CALL_ACCEPTED: /* connected */
+ retry = call_accepted(card, cmd, lcn, mb);
+ break;
+
+ case ASE_CLEAR_RQST: /* call clear request */
+ retry = call_cleared(card, cmd, lcn, mb);
+ break;
+
+ case ASE_RESET_RQST: /* reset request */
+ printk(KERN_INFO "%s: X.25 reset request on LCN %d! "
+ "Cause:0x%02X Diagn:0x%02X\n",
+ card->devname, mb->cmd.lcn, mb->cmd.cause,
+ mb->cmd.diagn);
+ api_oob_event (card,mb);
+ break;
+
+ case ASE_RESTART_RQST: /* restart request */
+ retry = restart_event(card, cmd, lcn, mb);
+ break;
+
+ case ASE_CLEAR_CONFRM:
+ if (clear_confirm_event (card,mb))
+ break;
+
+ /* I use the goto statement here so if
+ * somebody inserts code between the
+ * case and default, we will not have
+ * ghost problems */
+
+ goto dflt_1;
+
+ default:
+dflt_1:
+ printk(KERN_INFO "%s: X.25 event 0x%02X on LCN %d! "
+ "Cause:0x%02X Diagn:0x%02X\n",
+ card->devname, mb->cmd.pktType,
+ mb->cmd.lcn, mb->cmd.cause, mb->cmd.diagn);
+ }
+ break;
+
+ case X25RES_PROTO_VIOLATION: /* X.25 protocol violation indication */
+
+ /* Bug Fix: Mar 14 2000
+ * The Protocol violation error conditions were
+ * not handled previously */
+
+ switch (mb->cmd.pktType & 0x7F){
+
+ case PVE_CLEAR_RQST: /* Clear request */
+ retry = call_cleared(card, cmd, lcn, mb);
+ break;
+
+ case PVE_RESET_RQST: /* Reset request */
+ printk(KERN_INFO "%s: X.25 reset request on LCN %d! "
+ "Cause:0x%02X Diagn:0x%02X\n",
+ card->devname, mb->cmd.lcn, mb->cmd.cause,
+ mb->cmd.diagn);
+ api_oob_event (card,mb);
+ break;
+
+ case PVE_RESTART_RQST: /* Restart request */
+ retry = restart_event(card, cmd, lcn, mb);
+ break;
+
+ default :
+ printk(KERN_INFO
+ "%s: X.25 protocol violation on LCN %d! "
+ "Packet:0x%02X Cause:0x%02X Diagn:0x%02X\n",
+ card->devname, mb->cmd.lcn,
+ mb->cmd.pktType & 0x7F, mb->cmd.cause, mb->cmd.diagn);
+ api_oob_event(card,mb);
+ }
+ break;
+
+ case 0x42: /* X.25 timeout */
+ retry = timeout_event(card, cmd, lcn, mb);
+ break;
+
+ case 0x43: /* X.25 retry limit exceeded */
+ printk(KERN_INFO
+ "%s: exceeded X.25 retry limit on LCN %d! "
+ "Packet:0x%02X Diagn:0x%02X\n", card->devname,
+ mb->cmd.lcn, mb->cmd.pktType, mb->cmd.diagn)
+ ;
+ break;
+
+ case 0x08: /* modem failure */
+#ifndef MODEM_NOT_LOG
+ printk(KERN_INFO "%s: modem failure!\n", card->devname);
+#endif /* MODEM_NOT_LOG */
+ api_oob_event(card,mb);
+ break;
+
+ case 0x09: /* N2 retry limit */
+ printk(KERN_INFO "%s: exceeded HDLC retry limit!\n",
+ card->devname);
+ api_oob_event(card,mb);
+ break;
+
+ case 0x06: /* unnumbered frame was received while in ABM */
+ printk(KERN_INFO "%s: received Unnumbered frame 0x%02X!\n",
+ card->devname, mb->data[0]);
+ api_oob_event(card,mb);
+ break;
+
+ case CMD_TIMEOUT:
+ printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+ card->devname, cmd)
+ ;
+ retry = 0; /* abort command */
+ break;
+
+ case X25RES_NOT_READY:
+ retry = 1;
+ break;
+
+ case 0x01:
+ if (card->u.x.LAPB_hdlc)
+ break;
+
+ if (mb->cmd.command == 0x16)
+ break;
+ /* I use the goto statement here so if
+ * somebody inserts code between the
+ * case and default, we will not have
+ * ghost problems */
+ goto dflt_2;
+
+ default:
+dflt_2:
+ printk(KERN_INFO "%s: command 0x%02X returned 0x%02X! Lcn %i\n",
+ card->devname, cmd, err, mb->cmd.lcn)
+ ;
+ retry = 0; /* abort command */
+ }
+ kfree(mb);
+ return retry;
+}
+
+/*====================================================================
+ * X.25 Asynchronous Event Handlers
+ * These functions are called by the x25_error() and should return 0, if
+ * the command resulting in the asynchronous event must be aborted.
+ *====================================================================*/
+
+
+
+/*====================================================================
+ *Handle X.25 incoming call request.
+ * RFC 1356 establishes the following rules:
+ * 1. The first octet in the Call User Data (CUD) field of the call
+ * request packet contains NLPID identifying protocol encapsulation
+ * 2. Calls MUST NOT be accepted unless router supports requested
+ * protocol encapsulation.
+ * 3. A diagnostic code 249 defined by ISO/IEC 8208 may be used
+ * when clearing a call because protocol encapsulation is not
+ * supported.
+ * 4. If an incoming call is received while a call request is
+ * pending (i.e. call collision has occurred), the incoming call
+ * shall be rejected and call request shall be retried.
+ *====================================================================*/
+
+static int incoming_call (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+ struct wan_device* wandev = &card->wandev;
+ int new_lcn = mb->cmd.lcn;
+ struct net_device* dev = get_dev_by_lcn(wandev, new_lcn);
+ x25_channel_t* chan = NULL;
+ int accept = 0; /* set to '1' if o.k. to accept call */
+ unsigned int user_data;
+ x25_call_info_t* info;
+
+ /* Make sure there is no call collision */
+ if (dev != NULL)
+ {
+ printk(KERN_INFO
+ "%s: X.25 incoming call collision on LCN %d!\n",
+ card->devname, new_lcn);
+
+ x25_clear_call(card, new_lcn, 0, 0);
+ return 1;
+ }
+
+ /* Make sure D bit is not set in call request */
+//FIXME: THIS IS NOT TURE !!!! TAKE IT OUT
+// if (mb->cmd.qdm & 0x02)
+// {
+// printk(KERN_INFO
+// "%s: X.25 incoming call on LCN %d with D-bit set!\n",
+// card->devname, new_lcn);
+//
+// x25_clear_call(card, new_lcn, 0, 0);
+// return 1;
+// }
+
+ /* Parse call request data */
+ info = kmalloc(sizeof(x25_call_info_t), GFP_ATOMIC);
+ if (info == NULL)
+ {
+ printk(KERN_ERR
+ "%s: not enough memory to parse X.25 incoming call "
+ "on LCN %d!\n", card->devname, new_lcn);
+ x25_clear_call(card, new_lcn, 0, 0);
+ return 1;
+ }
+
+ parse_call_info(mb->data, info);
+
+ if (card->u.x.logging)
+ printk(KERN_INFO "\n%s: X.25 incoming call on LCN %d!\n",
+ card->devname, new_lcn);
+
+ /* Conver the first two ASCII characters into an
+ * interger. Used to check the incoming protocol
+ */
+ user_data = hex_to_uint(info->user,2);
+
+ /* Find available channel */
+ for (dev = wandev->dev; dev; dev = *((struct net_device **)dev->priv)) {
+ chan = dev->priv;
+
+ if (chan->common.usedby == API)
+ continue;
+
+ if (!chan->common.svc || (chan->common.state != WAN_DISCONNECTED))
+ continue;
+
+ if (user_data == NLPID_IP && chan->protocol != htons(ETH_P_IP)){
+ printk(KERN_INFO "IP packet but configured for IPX : %x, %x\n",
+ htons(chan->protocol), info->user[0]);
+ continue;
+ }
+
+ if (user_data == NLPID_SNAP && chan->protocol != htons(ETH_P_IPX)){
+ printk(KERN_INFO "IPX packet but configured for IP: %x\n",
+ htons(chan->protocol));
+ continue;
+ }
+ if (strcmp(info->src, chan->addr) == 0)
+ break;
+
+ /* If just an '@' is specified, accept all incoming calls */
+ if (strcmp(chan->addr, "") == 0)
+ break;
+ }
+
+ if (dev == NULL){
+
+ /* If the call is not for any WANPIPE interfaces
+ * check to see if there is an API listening queue
+ * waiting for data. If there is send the packet
+ * up the stack.
+ */
+ if (card->sk != NULL && card->func != NULL){
+ if (api_incoming_call(card,mb,new_lcn)){
+ x25_clear_call(card, new_lcn, 0, 0);
+ }
+ accept = 0;
+ }else{
+ printk(KERN_INFO "%s: no channels available!\n",
+ card->devname);
+
+ x25_clear_call(card, new_lcn, 0, 0);
+ }
+
+ }else if (info->nuser == 0){
+
+ printk(KERN_INFO
+ "%s: no user data in incoming call on LCN %d!\n",
+ card->devname, new_lcn)
+ ;
+ x25_clear_call(card, new_lcn, 0, 0);
+
+ }else switch (info->user[0]){
+
+ case 0: /* multiplexed */
+ chan->protocol = htons(0);
+ accept = 1;
+ break;
+
+ case NLPID_IP: /* IP datagrams */
+ accept = 1;
+ break;
+
+ case NLPID_SNAP: /* IPX datagrams */
+ accept = 1;
+ break;
+
+ default:
+ printk(KERN_INFO
+ "%s: unsupported NLPID 0x%02X in incoming call "
+ "on LCN %d!\n", card->devname, info->user[0], new_lcn);
+ x25_clear_call(card, new_lcn, 0, 249);
+ }
+
+ if (accept && (x25_accept_call(card, new_lcn, 0) == CMD_OK)){
+
+ bind_lcn_to_dev (card, chan->dev, new_lcn);
+
+ if (x25_get_chan_conf(card, chan) == CMD_OK)
+ set_chan_state(dev, WAN_CONNECTED);
+ else
+ x25_clear_call(card, new_lcn, 0, 0);
+ }
+ kfree(info);
+ return 1;
+}
+
+/*====================================================================
+ * Handle accepted call.
+ *====================================================================*/
+
+static int call_accepted (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+ unsigned new_lcn = mb->cmd.lcn;
+ struct net_device* dev = find_channel(card, new_lcn);
+ x25_channel_t* chan;
+
+ if (dev == NULL){
+ printk(KERN_INFO
+ "%s: clearing orphaned connection on LCN %d!\n",
+ card->devname, new_lcn);
+ x25_clear_call(card, new_lcn, 0, 0);
+ return 1;
+ }
+
+ if (card->u.x.logging)
+ printk(KERN_INFO "%s: X.25 call accepted on Dev %s and LCN %d!\n",
+ card->devname, dev->name, new_lcn);
+
+ /* Get channel configuration and notify router */
+ chan = dev->priv;
+ if (x25_get_chan_conf(card, chan) != CMD_OK)
+ {
+ x25_clear_call(card, new_lcn, 0, 0);
+ return 1;
+ }
+
+ set_chan_state(dev, WAN_CONNECTED);
+
+ if (chan->common.usedby == API){
+ send_delayed_cmd_result(card,dev,mb);
+ bind_lcn_to_dev (card, dev, new_lcn);
+ }
+
+ return 1;
+}
+
+/*====================================================================
+ * Handle cleared call.
+ *====================================================================*/
+
+static int call_cleared (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+ unsigned new_lcn = mb->cmd.lcn;
+ struct net_device* dev = find_channel(card, new_lcn);
+ x25_channel_t *chan;
+ unsigned char old_state;
+
+ if (card->u.x.logging){
+ printk(KERN_INFO "%s: X.25 clear request on LCN %d! Cause:0x%02X "
+ "Diagn:0x%02X\n",
+ card->devname, new_lcn, mb->cmd.cause, mb->cmd.diagn);
+ }
+
+ if (dev == NULL){
+ printk(KERN_INFO "%s: X.25 clear request : No device for clear\n",
+ card->devname);
+ return 1;
+ }
+
+ chan=dev->priv;
+
+ old_state = chan->common.state;
+
+ set_chan_state(dev, WAN_DISCONNECTED);
+
+ if (chan->common.usedby == API){
+
+ switch (old_state){
+
+ case WAN_CONNECTING:
+ send_delayed_cmd_result(card,dev,mb);
+ break;
+ case WAN_CONNECTED:
+ send_oob_msg(card,dev,mb);
+ break;
+ }
+ }
+
+ return ((cmd == X25_WRITE) && (lcn == new_lcn)) ? 0 : 1;
+}
+
+/*====================================================================
+ * Handle X.25 restart event.
+ *====================================================================*/
+
+static int restart_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+ struct wan_device* wandev = &card->wandev;
+ struct net_device* dev;
+ x25_channel_t *chan;
+ unsigned char old_state;
+
+ printk(KERN_INFO
+ "%s: X.25 restart request! Cause:0x%02X Diagn:0x%02X\n",
+ card->devname, mb->cmd.cause, mb->cmd.diagn);
+
+ /* down all logical channels */
+ for (dev = wandev->dev; dev; dev = *((struct net_device **)dev->priv)) {
+ chan=dev->priv;
+ old_state = chan->common.state;
+
+ set_chan_state(dev, WAN_DISCONNECTED);
+
+ if (chan->common.usedby == API){
+ switch (old_state){
+
+ case WAN_CONNECTING:
+ send_delayed_cmd_result(card,dev,mb);
+ break;
+ case WAN_CONNECTED:
+ send_oob_msg(card,dev,mb);
+ break;
+ }
+ }
+ }
+ return (cmd == X25_WRITE) ? 0 : 1;
+}
+
+/*====================================================================
+ * Handle timeout event.
+ *====================================================================*/
+
+static int timeout_event (sdla_t* card, int cmd, int lcn, TX25Mbox* mb)
+{
+ unsigned new_lcn = mb->cmd.lcn;
+
+ if (mb->cmd.pktType == 0x05) /* call request time out */
+ {
+ struct net_device* dev = find_channel(card,new_lcn);
+
+ printk(KERN_INFO "%s: X.25 call timed timeout on LCN %d!\n",
+ card->devname, new_lcn);
+
+ if (dev){
+ x25_channel_t *chan = dev->priv;
+ set_chan_state(dev, WAN_DISCONNECTED);
+
+ if (chan->common.usedby == API){
+ send_delayed_cmd_result(card,dev,card->mbox);
+ }
+ }
+ }else{
+ printk(KERN_INFO "%s: X.25 packet 0x%02X timeout on LCN %d!\n",
+ card->devname, mb->cmd.pktType, new_lcn);
+ }
+ return 1;
+}
+
+/*
+ * Miscellaneous
+ */
+
+/*====================================================================
+ * Establish physical connection.
+ * o open HDLC and raise DTR
+ *
+ * Return: 0 connection established
+ * 1 connection is in progress
+ * <0 error
+ *===================================================================*/
+
+static int connect (sdla_t* card)
+{
+ TX25Status* status = card->flags;
+
+ if (x25_open_hdlc(card) || x25_setup_hdlc(card))
+ return -EIO;
+
+ wanpipe_set_state(card, WAN_CONNECTING);
+
+ x25_set_intr_mode(card, INTR_ON_TIMER);
+ status->imask &= ~INTR_ON_TIMER;
+
+ return 1;
+}
+
+/*
+ * Tear down physical connection.
+ * o close HDLC link
+ * o drop DTR
+ *
+ * Return: 0
+ * <0 error
+ */
+
+static int disconnect (sdla_t* card)
+{
+ wanpipe_set_state(card, WAN_DISCONNECTED);
+ x25_set_intr_mode(card, INTR_ON_TIMER); /* disable all interrupt except timer */
+ x25_close_hdlc(card); /* close HDLC link */
+ x25_set_dtr(card, 0); /* drop DTR */
+ return 0;
+}
+
+/*
+ * Find network device by its channel number.
+ */
+
+static struct net_device* get_dev_by_lcn(struct wan_device* wandev,
+ unsigned lcn)
+{
+ struct net_device* dev;
+
+ for (dev = wandev->dev; dev; dev = *((struct net_device **)dev->priv))
+ if (((x25_channel_t*)dev->priv)->common.lcn == lcn)
+ break;
+ return dev;
+}
+
+/*
+ * Initiate connection on the logical channel.
+ * o for PVC we just get channel configuration
+ * o for SVCs place an X.25 call
+ *
+ * Return: 0 connected
+ * >0 connection in progress
+ * <0 failure
+ */
+
+static int chan_connect(struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+
+ if (chan->common.svc && chan->common.usedby == WANPIPE){
+ if (!chan->addr[0]){
+ printk(KERN_INFO "%s: No Destination Address\n",
+ card->devname);
+ return -EINVAL; /* no destination address */
+ }
+ printk(KERN_INFO "%s: placing X.25 call to %s ...\n",
+ card->devname, chan->addr);
+
+ if (x25_place_call(card, chan) != CMD_OK)
+ return -EIO;
+
+ set_chan_state(dev, WAN_CONNECTING);
+ return 1;
+ }else{
+ if (x25_get_chan_conf(card, chan) != CMD_OK)
+ return -EIO;
+
+ set_chan_state(dev, WAN_CONNECTED);
+ }
+ return 0;
+}
+
+/*
+ * Disconnect logical channel.
+ * o if SVC then clear X.25 call
+ */
+
+static int chan_disc(struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+
+ if (chan->common.svc){
+ x25_clear_call(chan->card, chan->common.lcn, 0, 0);
+
+ /* For API we disconnect on clear
+ * confirmation.
+ */
+ if (chan->common.usedby == API)
+ return 0;
+ }
+
+ set_chan_state(dev, WAN_DISCONNECTED);
+
+ return 0;
+}
+
+/*
+ * Set logical channel state.
+ */
+
+static void set_chan_state(struct net_device* dev, int state)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ if (chan->common.state != state)
+ {
+ switch (state)
+ {
+ case WAN_CONNECTED:
+ if (card->u.x.logging){
+ printk (KERN_INFO
+ "%s: interface %s connected, lcn %i !\n",
+ card->devname, dev->name,chan->common.lcn);
+ }
+ *(unsigned short*)dev->dev_addr = htons(chan->common.lcn);
+ chan->i_timeout_sofar = jiffies;
+
+ /* LAPB is PVC Based */
+ if (card->u.x.LAPB_hdlc)
+ chan->common.svc=0;
+ break;
+
+ case WAN_CONNECTING:
+ if (card->u.x.logging){
+ printk (KERN_INFO
+ "%s: interface %s connecting, lcn %i ...\n",
+ card->devname, dev->name, chan->common.lcn);
+ }
+ break;
+
+ case WAN_DISCONNECTED:
+ if (card->u.x.logging){
+ printk (KERN_INFO
+ "%s: interface %s disconnected, lcn %i !\n",
+ card->devname, dev->name,chan->common.lcn);
+ }
+ atomic_set(&chan->common.disconnect,0);
+
+ if (chan->common.svc) {
+ *(unsigned short*)dev->dev_addr = 0;
+ card->u.x.svc_to_dev_map[(chan->common.lcn%X25_MAX_CHAN)]=NULL;
+ chan->common.lcn = 0;
+ }
+
+ if (chan->transmit_length){
+ chan->transmit_length=0;
+ atomic_set(&chan->common.driver_busy,0);
+ chan->tx_offset=0;
+ if (netif_queue_stopped(dev)){
+ netif_wake_queue(dev);
+ }
+ }
+ atomic_set(&chan->common.command,0);
+ break;
+
+ case WAN_DISCONNECTING:
+ if (card->u.x.logging){
+ printk (KERN_INFO
+ "\n%s: interface %s disconnecting, lcn %i ...\n",
+ card->devname, dev->name,chan->common.lcn);
+ }
+ atomic_set(&chan->common.disconnect,0);
+ break;
+ }
+ chan->common.state = state;
+ }
+ chan->state_tick = jiffies;
+ restore_flags(flags);
+}
+
+/*
+ * Send packet on a logical channel.
+ * When this function is called, tx_skb field of the channel data
+ * space points to the transmit socket buffer. When transmission
+ * is complete, release socket buffer and reset 'tbusy' flag.
+ *
+ * Return: 0 - transmission complete
+ * 1 - busy
+ *
+ * Notes:
+ * 1. If packet length is greater than MTU for this channel, we'll fragment
+ * the packet into 'complete sequence' using M-bit.
+ * 2. When transmission is complete, an event notification should be issued
+ * to the router.
+ */
+
+static int chan_send(struct net_device* dev, void* buff, unsigned data_len,
+ unsigned char tx_intr)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ TX25Status* status = card->flags;
+ unsigned len=0, qdm=0, res=0, orig_len = 0;
+ void *data;
+
+ /* Check to see if channel is ready */
+ if ((!(status->cflags[chan->ch_idx] & 0x40) && !card->u.x.LAPB_hdlc) ||
+ !(*card->u.x.hdlc_buf_status & 0x40)){
+
+ if (!tx_intr){
+ setup_for_delayed_transmit (dev, buff, data_len);
+ return 0;
+ }else{
+ /* By returning 0 to tx_intr the packet will be dropped */
+ ++card->wandev.stats.tx_dropped;
+ ++chan->ifstats.tx_dropped;
+ printk(KERN_INFO "%s: ERROR, Tx intr could not send, dropping %s:\n",
+ card->devname,dev->name);
+ ++chan->if_send_stat.if_send_bfr_not_passed_to_adptr;
+ return 0;
+ }
+ }
+
+ if (chan->common.usedby == API){
+ /* Remove the API Header */
+ x25api_hdr_t *api_data = (x25api_hdr_t *)buff;
+
+ /* Set the qdm bits from the packet header
+ * User has the option to set the qdm bits
+ */
+ qdm = api_data->qdm;
+
+ orig_len = len = data_len - sizeof(x25api_hdr_t);
+ data = (unsigned char*)buff + sizeof(x25api_hdr_t);
+ }else{
+ data = buff;
+ orig_len = len = data_len;
+ }
+
+ if (tx_intr){
+ /* We are in tx_intr, minus the tx_offset from
+ * the total length. The tx_offset part of the
+ * data has already been sent. Also, move the
+ * data pointer to proper offset location.
+ */
+ len -= chan->tx_offset;
+ data = (unsigned char*)data + chan->tx_offset;
+ }
+
+ /* Check if the packet length is greater than MTU
+ * If YES: Cut the len to MTU and set the M bit
+ */
+ if (len > chan->tx_pkt_size && !card->u.x.LAPB_hdlc){
+ len = chan->tx_pkt_size;
+ qdm |= M_BIT;
+ }
+
+
+ /* Pass only first three bits of the qdm byte to the send
+ * routine. In case user sets any other bit which might
+ * cause errors.
+ */
+
+ switch(x25_send(card, chan->common.lcn, (qdm&0x07), len, data)){
+ case 0x00: /* success */
+ chan->i_timeout_sofar = jiffies;
+
+ dev->trans_start=jiffies;
+
+ if ((qdm & M_BIT) && !card->u.x.LAPB_hdlc){
+ if (!tx_intr){
+ /* The M bit was set, which means that part of the
+ * packet has been sent. Copy the packet into a buffer
+ * and set the offset to len, so on next tx_inter
+ * the packet will be sent using the below offset.
+ */
+ chan->tx_offset += len;
+
+ ++chan->ifstats.tx_packets;
+ chan->ifstats.tx_bytes += len;
+
+ if (chan->tx_offset < orig_len){
+ setup_for_delayed_transmit (dev, buff, data_len);
+ }
+ res=0;
+ }else{
+ /* We are already in tx_inter, thus data is already
+ * in the buffer. Update the offset and wait for
+ * next tx_intr. We add on to the offset, since data can
+ * be X number of times larger than max data size.
+ */
+ ++chan->ifstats.tx_packets;
+ chan->ifstats.tx_bytes += len;
+
+ ++chan->if_send_stat.if_send_bfr_passed_to_adptr;
+ chan->tx_offset += len;
+
+ /* The user can set the qdm bit as well.
+ * If the entire packet was sent and qdm is still
+ * set, than it's the user who has set the M bit. In that,
+ * case indicate that the packet was send by returning
+ * 0 and wait for a new packet. Otherwise, wait for next
+ * tx interrupt to send the rest of the packet */
+
+ if (chan->tx_offset < orig_len){
+ res=1;
+ }else{
+ res=0;
+ }
+ }
+ }else{
+ ++chan->ifstats.tx_packets;
+ chan->ifstats.tx_bytes += len;
+ ++chan->if_send_stat.if_send_bfr_passed_to_adptr;
+ res=0;
+ }
+ break;
+
+ case 0x33: /* Tx busy */
+ if (tx_intr){
+ printk(KERN_INFO "%s: Tx_intr: Big Error dropping packet %s\n",
+ card->devname,dev->name);
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ ++chan->if_send_stat.if_send_bfr_not_passed_to_adptr;
+ res=0;
+ }else{
+ DBG_PRINTK(KERN_INFO
+ "%s: Send: Big Error should have tx: storring %s\n",
+ card->devname,dev->name);
+ setup_for_delayed_transmit (dev, buff, data_len);
+ res=1;
+ }
+ break;
+
+ default: /* failure */
+ ++chan->ifstats.tx_errors;
+ if (tx_intr){
+ printk(KERN_INFO "%s: Tx_intr: Failure to send, dropping %s\n",
+ card->devname,dev->name);
+ ++chan->ifstats.tx_dropped;
+ ++card->wandev.stats.tx_dropped;
+ ++chan->if_send_stat.if_send_bfr_not_passed_to_adptr;
+ res=0;
+ }else{
+ DBG_PRINTK(KERN_INFO "%s: Send: Failure to send !!!, storing %s\n",
+ card->devname,dev->name);
+ setup_for_delayed_transmit (dev, buff, data_len);
+ res=1;
+ }
+ break;
+ }
+ return res;
+}
+
+
+/*
+ * Parse X.25 call request data and fill x25_call_info_t structure.
+ */
+
+static void parse_call_info (unsigned char* str, x25_call_info_t* info)
+{
+ memset(info, 0, sizeof(x25_call_info_t));
+ for (; *str; ++str)
+ {
+ int i;
+ unsigned char ch;
+
+ if (*str == '-') switch (str[1]) {
+
+ /* Take minus 2 off the maximum size so that
+ * last byte is 0. This way we can use string
+ * manipulaton functions on call information.
+ */
+
+ case 'd': /* destination address */
+ for (i = 0; i < (MAX_X25_ADDR_SIZE-2); ++i){
+ ch = str[2+i];
+ if (isspace(ch)) break;
+ info->dest[i] = ch;
+ }
+ break;
+
+ case 's': /* source address */
+ for (i = 0; i < (MAX_X25_ADDR_SIZE-2); ++i){
+ ch = str[2+i];
+ if (isspace(ch)) break;
+ info->src[i] = ch;
+ }
+ break;
+
+ case 'u': /* user data */
+ for (i = 0; i < (MAX_X25_DATA_SIZE-2); ++i){
+ ch = str[2+i];
+ if (isspace(ch)) break;
+ info->user[i] = ch;
+ }
+ info->nuser = i;
+ break;
+
+ case 'f': /* facilities */
+ for (i = 0; i < (MAX_X25_FACL_SIZE-2); ++i){
+ ch = str[2+i];
+ if (isspace(ch)) break;
+ info->facil[i] = ch;
+ }
+ info->nfacil = i;
+ break;
+ }
+ }
+}
+
+/*
+ * Convert line speed in bps to a number used by S502 code.
+ */
+
+static unsigned char bps_to_speed_code (unsigned long bps)
+{
+ unsigned char number;
+
+ if (bps <= 1200) number = 0x01;
+ else if (bps <= 2400) number = 0x02;
+ else if (bps <= 4800) number = 0x03;
+ else if (bps <= 9600) number = 0x04;
+ else if (bps <= 19200) number = 0x05;
+ else if (bps <= 38400) number = 0x06;
+ else if (bps <= 45000) number = 0x07;
+ else if (bps <= 56000) number = 0x08;
+ else if (bps <= 64000) number = 0x09;
+ else if (bps <= 74000) number = 0x0A;
+ else if (bps <= 112000) number = 0x0B;
+ else if (bps <= 128000) number = 0x0C;
+ else number = 0x0D;
+
+ return number;
+}
+
+/*
+ * Convert decimal string to unsigned integer.
+ * If len != 0 then only 'len' characters of the string are converted.
+ */
+
+static unsigned int dec_to_uint (unsigned char* str, int len)
+{
+ unsigned val;
+
+ if (!len)
+ len = strlen(str);
+
+ for (val = 0; len && is_digit(*str); ++str, --len)
+ val = (val * 10) + (*str - (unsigned)'0');
+
+ return val;
+}
+
+/*
+ * Convert hex string to unsigned integer.
+ * If len != 0 then only 'len' characters of the string are conferted.
+ */
+
+static unsigned int hex_to_uint (unsigned char* str, int len)
+{
+ unsigned val, ch;
+
+ if (!len)
+ len = strlen(str);
+
+ for (val = 0; len; ++str, --len)
+ {
+ ch = *str;
+ if (is_digit(ch))
+ val = (val << 4) + (ch - (unsigned)'0');
+ else if (is_hex_digit(ch))
+ val = (val << 4) + ((ch & 0xDF) - (unsigned)'A' + 10);
+ else break;
+ }
+ return val;
+}
+
+
+static int handle_IPXWAN(unsigned char *sendpacket, char *devname, unsigned char enable_IPX, unsigned long network_number, unsigned short proto)
+{
+ int i;
+
+ if( proto == ETH_P_IPX) {
+ /* It's an IPX packet */
+ if(!enable_IPX) {
+ /* Return 1 so we don't pass it up the stack. */
+ return 1;
+ }
+ } else {
+ /* It's not IPX so pass it up the stack.*/
+ return 0;
+ }
+
+ if( sendpacket[16] == 0x90 &&
+ sendpacket[17] == 0x04)
+ {
+ /* It's IPXWAN */
+
+ if( sendpacket[2] == 0x02 &&
+ sendpacket[34] == 0x00)
+ {
+ /* It's a timer request packet */
+ printk(KERN_INFO "%s: Received IPXWAN Timer Request packet\n",devname);
+
+ /* Go through the routing options and answer no to every
+ * option except Unnumbered RIP/SAP
+ */
+ for(i = 41; sendpacket[i] == 0x00; i += 5)
+ {
+ /* 0x02 is the option for Unnumbered RIP/SAP */
+ if( sendpacket[i + 4] != 0x02)
+ {
+ sendpacket[i + 1] = 0;
+ }
+ }
+
+ /* Skip over the extended Node ID option */
+ if( sendpacket[i] == 0x04 )
+ {
+ i += 8;
+ }
+
+ /* We also want to turn off all header compression opt. */
+ for(; sendpacket[i] == 0x80 ;)
+ {
+ sendpacket[i + 1] = 0;
+ i += (sendpacket[i + 2] << 8) + (sendpacket[i + 3]) + 4;
+ }
+
+ /* Set the packet type to timer response */
+ sendpacket[34] = 0x01;
+
+ printk(KERN_INFO "%s: Sending IPXWAN Timer Response\n",devname);
+ }
+ else if( sendpacket[34] == 0x02 )
+ {
+ /* This is an information request packet */
+ printk(KERN_INFO "%s: Received IPXWAN Information Request packet\n",devname);
+
+ /* Set the packet type to information response */
+ sendpacket[34] = 0x03;
+
+ /* Set the router name */
+ sendpacket[51] = 'X';
+ sendpacket[52] = 'T';
+ sendpacket[53] = 'P';
+ sendpacket[54] = 'I';
+ sendpacket[55] = 'P';
+ sendpacket[56] = 'E';
+ sendpacket[57] = '-';
+ sendpacket[58] = CVHexToAscii(network_number >> 28);
+ sendpacket[59] = CVHexToAscii((network_number & 0x0F000000)>> 24);
+ sendpacket[60] = CVHexToAscii((network_number & 0x00F00000)>> 20);
+ sendpacket[61] = CVHexToAscii((network_number & 0x000F0000)>> 16);
+ sendpacket[62] = CVHexToAscii((network_number & 0x0000F000)>> 12);
+ sendpacket[63] = CVHexToAscii((network_number & 0x00000F00)>> 8);
+ sendpacket[64] = CVHexToAscii((network_number & 0x000000F0)>> 4);
+ sendpacket[65] = CVHexToAscii(network_number & 0x0000000F);
+ for(i = 66; i < 99; i+= 1)
+ {
+ sendpacket[i] = 0;
+ }
+
+ printk(KERN_INFO "%s: Sending IPXWAN Information Response packet\n",devname);
+ }
+ else
+ {
+ printk(KERN_INFO "%s: Unknown IPXWAN packet!\n",devname);
+ return 0;
+ }
+
+ /* Set the WNodeID to our network address */
+ sendpacket[35] = (unsigned char)(network_number >> 24);
+ sendpacket[36] = (unsigned char)((network_number & 0x00FF0000) >> 16);
+ sendpacket[37] = (unsigned char)((network_number & 0x0000FF00) >> 8);
+ sendpacket[38] = (unsigned char)(network_number & 0x000000FF);
+
+ return 1;
+ } else {
+ /*If we get here it's an IPX-data packet, so it'll get passed up the stack.
+ */
+ /* switch the network numbers */
+ switch_net_numbers(sendpacket, network_number, 1);
+ return 0;
+ }
+}
+
+/*
+ * If incoming is 0 (outgoing)- if the net numbers is ours make it 0
+ * if incoming is 1 - if the net number is 0 make it ours
+ */
+
+static void switch_net_numbers(unsigned char *sendpacket, unsigned long network_number, unsigned char incoming)
+{
+ unsigned long pnetwork_number;
+
+ pnetwork_number = (unsigned long)((sendpacket[6] << 24) +
+ (sendpacket[7] << 16) + (sendpacket[8] << 8) +
+ sendpacket[9]);
+
+
+ if (!incoming) {
+ /*If the destination network number is ours, make it 0 */
+ if( pnetwork_number == network_number) {
+ sendpacket[6] = sendpacket[7] = sendpacket[8] =
+ sendpacket[9] = 0x00;
+ }
+ } else {
+ /* If the incoming network is 0, make it ours */
+ if( pnetwork_number == 0) {
+ sendpacket[6] = (unsigned char)(network_number >> 24);
+ sendpacket[7] = (unsigned char)((network_number &
+ 0x00FF0000) >> 16);
+ sendpacket[8] = (unsigned char)((network_number &
+ 0x0000FF00) >> 8);
+ sendpacket[9] = (unsigned char)(network_number &
+ 0x000000FF);
+ }
+ }
+
+
+ pnetwork_number = (unsigned long)((sendpacket[18] << 24) +
+ (sendpacket[19] << 16) + (sendpacket[20] << 8) +
+ sendpacket[21]);
+
+
+ if( !incoming ) {
+ /* If the source network is ours, make it 0 */
+ if( pnetwork_number == network_number) {
+ sendpacket[18] = sendpacket[19] = sendpacket[20] =
+ sendpacket[21] = 0x00;
+ }
+ } else {
+ /* If the source network is 0, make it ours */
+ if( pnetwork_number == 0 ) {
+ sendpacket[18] = (unsigned char)(network_number >> 24);
+ sendpacket[19] = (unsigned char)((network_number &
+ 0x00FF0000) >> 16);
+ sendpacket[20] = (unsigned char)((network_number &
+ 0x0000FF00) >> 8);
+ sendpacket[21] = (unsigned char)(network_number &
+ 0x000000FF);
+ }
+ }
+} /* switch_net_numbers */
+
+
+
+
+/********************* X25API SPECIFIC FUNCTIONS ****************/
+
+
+/*===============================================================
+ * find_channel
+ *
+ * Manages the lcn to device map. It increases performance
+ * because it eliminates the need to search through the link
+ * list for a device which is bounded to a specific lcn.
+ *
+ *===============================================================*/
+
+
+struct net_device *find_channel(sdla_t *card, unsigned lcn)
+{
+ if (card->u.x.LAPB_hdlc){
+
+ return card->wandev.dev;
+
+ }else{
+ /* We don't know whether the incoming lcn
+ * is a PVC or an SVC channel. But we do know that
+ * the lcn cannot be for both the PVC and the SVC
+ * channel.
+
+ * If the lcn number is greater or equal to 255,
+ * take the modulo 255 of that number. We only have
+ * 255 locations, thus higher numbers must be mapped
+ * to a number between 0 and 245.
+
+ * We must separate pvc's and svc's since two don't
+ * have to be contiguous. Meaning pvc's can start
+ * from 1 to 10 and svc's can start from 256 to 266.
+ * But 256%255 is 1, i.e. CONFLICT.
+ */
+
+
+ /* Highest LCN number must be less or equal to 4096 */
+ if ((lcn <= MAX_LCN_NUM) && (lcn > 0)){
+
+ if (lcn < X25_MAX_CHAN){
+ if (card->u.x.svc_to_dev_map[lcn])
+ return card->u.x.svc_to_dev_map[lcn];
+
+ if (card->u.x.pvc_to_dev_map[lcn])
+ return card->u.x.pvc_to_dev_map[lcn];
+
+ }else{
+ int new_lcn = lcn%X25_MAX_CHAN;
+ if (card->u.x.svc_to_dev_map[new_lcn])
+ return card->u.x.svc_to_dev_map[new_lcn];
+
+ if (card->u.x.pvc_to_dev_map[new_lcn])
+ return card->u.x.pvc_to_dev_map[new_lcn];
+ }
+ }
+ return NULL;
+ }
+}
+
+void bind_lcn_to_dev(sdla_t *card, struct net_device *dev, unsigned lcn)
+{
+ x25_channel_t *chan = dev->priv;
+
+ /* Modulo the lcn number by X25_MAX_CHAN (255)
+ * because the lcn number can be greater than 255
+ *
+ * We need to split svc and pvc since they don't have
+ * to be contigous.
+ */
+
+ if (chan->common.svc){
+ card->u.x.svc_to_dev_map[(lcn % X25_MAX_CHAN)] = dev;
+ }else{
+ card->u.x.pvc_to_dev_map[(lcn % X25_MAX_CHAN)] = dev;
+ }
+ chan->common.lcn = lcn;
+}
+
+
+
+/*===============================================================
+ * x25api_bh
+ *
+ *
+ *==============================================================*/
+
+static void x25api_bh(struct net_device* dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t* card = chan->card;
+ struct sk_buff *skb;
+
+ if (atomic_read(&chan->bh_buff_used) == 0){
+ printk(KERN_INFO "%s: BH Buffer Empty in BH\n",
+ card->devname);
+ clear_bit(0, &chan->tq_working);
+ return;
+ }
+
+ while (atomic_read(&chan->bh_buff_used)){
+
+ /* If the sock is in the process of unlinking the
+ * driver from the socket, we must get out.
+ * This never happends but is a sanity check. */
+ if (test_bit(0,&chan->common.common_critical)){
+ clear_bit(0, &chan->tq_working);
+ return;
+ }
+
+ /* If LAPB HDLC, do not drop packets if socket is
+ * not connected. Let the buffer fill up and
+ * turn off rx interrupt */
+ if (card->u.x.LAPB_hdlc){
+ if (chan->common.sk == NULL || chan->common.func == NULL){
+ clear_bit(0, &chan->tq_working);
+ return;
+ }
+ }
+
+ skb = ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb;
+
+ if (skb == NULL){
+ printk(KERN_INFO "%s: BH Skb empty for read %i\n",
+ card->devname,chan->bh_read);
+ }else{
+
+ if (chan->common.sk == NULL || chan->common.func == NULL){
+ printk(KERN_INFO "%s: BH: Socket disconnected, dropping\n",
+ card->devname);
+ dev_kfree_skb_any(skb);
+ x25api_bh_cleanup(dev);
+ ++chan->ifstats.rx_dropped;
+ ++chan->rx_intr_stat.rx_intr_bfr_not_passed_to_stack;
+ continue;
+ }
+
+
+ if (chan->common.func(skb,dev,chan->common.sk) != 0){
+ /* Sock full cannot send, queue us for another
+ * try
+ */
+ printk(KERN_INFO "%s: BH: !!! Packet failed to send !!!!! \n",
+ card->devname);
+ atomic_set(&chan->common.receive_block,1);
+ return;
+ }else{
+ x25api_bh_cleanup(dev);
+ ++chan->rx_intr_stat.rx_intr_bfr_passed_to_stack;
+ }
+ }
+ }
+ clear_bit(0, &chan->tq_working);
+
+ return;
+}
+
+/*===============================================================
+ * x25api_bh_cleanup
+ *
+ *
+ *==============================================================*/
+
+static int x25api_bh_cleanup(struct net_device *dev)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+ TX25Status* status = card->flags;
+
+
+ ((bh_data_t *)&chan->bh_head[chan->bh_read])->skb = NULL;
+
+ if (chan->bh_read == MAX_BH_BUFF){
+ chan->bh_read=0;
+ }else{
+ ++chan->bh_read;
+ }
+
+ /* If the Receive interrupt was off, it means
+ * that we filled up our circular buffer. Check
+ * that we have space in the buffer. If so
+ * turn the RX interrupt back on.
+ */
+ if (!(status->imask & INTR_ON_RX_FRAME)){
+ if (atomic_read(&chan->bh_buff_used) < (MAX_BH_BUFF+1)){
+ printk(KERN_INFO "%s: BH: Turning on the interrupt\n",
+ card->devname);
+ status->imask |= INTR_ON_RX_FRAME;
+ }
+ }
+
+ atomic_dec(&chan->bh_buff_used);
+ return 0;
+}
+
+
+/*===============================================================
+ * bh_enqueue
+ *
+ *
+ *==============================================================*/
+
+static int bh_enqueue(struct net_device *dev, struct sk_buff *skb)
+{
+ x25_channel_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+ TX25Status* status = card->flags;
+
+ if (atomic_read(&chan->bh_buff_used) == (MAX_BH_BUFF+1)){
+ printk(KERN_INFO "%s: Bottom half buffer FULL\n",
+ card->devname);
+ return 1;
+ }
+
+ ((bh_data_t *)&chan->bh_head[chan->bh_write])->skb = skb;
+
+ if (chan->bh_write == MAX_BH_BUFF){
+ chan->bh_write=0;
+ }else{
+ ++chan->bh_write;
+ }
+
+ atomic_inc(&chan->bh_buff_used);
+
+ if (atomic_read(&chan->bh_buff_used) == (MAX_BH_BUFF+1)){
+ printk(KERN_INFO "%s: Buffer is now full, Turning off RX Intr\n",
+ card->devname);
+ status->imask &= ~INTR_ON_RX_FRAME;
+ }
+
+ return 0;
+}
+
+
+/*===============================================================
+ * timer_intr_cmd_exec
+ *
+ * Called by timer interrupt to execute a command
+ *===============================================================*/
+
+static int timer_intr_cmd_exec (sdla_t* card)
+{
+ struct net_device *dev;
+ unsigned char more_to_exec=0;
+ volatile x25_channel_t *chan=NULL;
+ int i=0,bad_cmd=0,err=0;
+
+ if (card->u.x.cmd_dev == NULL){
+ card->u.x.cmd_dev = card->wandev.dev;
+ }
+
+ dev = card->u.x.cmd_dev;
+
+ for (;;){
+
+ chan = dev->priv;
+
+ if (atomic_read(&chan->common.command)){
+
+ bad_cmd = check_bad_command(card,dev);
+
+ if ((!chan->common.mbox || atomic_read(&chan->common.disconnect)) &&
+ !bad_cmd){
+
+ /* Socket has died or exited, We must bring the
+ * channel down before anybody else tries to
+ * use it */
+ err = channel_disconnect(card,dev);
+ }else{
+ err = execute_delayed_cmd(card, dev,
+ (mbox_cmd_t*)chan->common.mbox,
+ bad_cmd);
+ }
+
+ switch (err){
+
+ case RETURN_RESULT:
+
+ /* Return the result to the socket without
+ * delay. NO_WAIT Command */
+ atomic_set(&chan->common.command,0);
+ if (atomic_read(&card->u.x.command_busy))
+ atomic_set(&card->u.x.command_busy,0);
+
+ send_delayed_cmd_result(card,dev,card->mbox);
+
+ more_to_exec=0;
+ break;
+ case DELAY_RESULT:
+
+ /* Wait for the remote to respond, before
+ * sending the result up to the socket.
+ * WAIT command */
+ if (atomic_read(&card->u.x.command_busy))
+ atomic_set(&card->u.x.command_busy,0);
+
+ atomic_set(&chan->common.command,0);
+ more_to_exec=0;
+ break;
+ default:
+
+ /* If command could not be executed for
+ * some reason (i.e return code 0x33 busy)
+ * set the more_to_exec bit which will
+ * indicate that this command must be exectued
+ * again during next timer interrupt
+ */
+ more_to_exec=1;
+ if (atomic_read(&card->u.x.command_busy) == 0)
+ atomic_set(&card->u.x.command_busy,1);
+ break;
+ }
+
+ bad_cmd=0;
+
+ /* If flags is set, there are no hdlc buffers,
+ * thus, wait for the next pass and try the
+ * same command again. Otherwise, start searching
+ * from next device on the next pass.
+ */
+ if (!more_to_exec){
+ dev = move_dev_to_next(card,dev);
+ }
+ break;
+ }else{
+ /* This device has nothing to execute,
+ * go to next.
+ */
+ if (atomic_read(&card->u.x.command_busy))
+ atomic_set(&card->u.x.command_busy,0);
+ dev = move_dev_to_next(card,dev);
+ }
+
+ if (++i == card->u.x.no_dev){
+ if (!more_to_exec){
+ DBG_PRINTK(KERN_INFO "%s: Nothing to execute in Timer\n",
+ card->devname);
+ if (atomic_read(&card->u.x.command_busy)){
+ atomic_set(&card->u.x.command_busy,0);
+ }
+ }
+ break;
+ }
+
+ } //End of FOR
+
+ card->u.x.cmd_dev = dev;
+
+ if (more_to_exec){
+ /* If more commands are pending, do not turn off timer
+ * interrupt */
+ return 1;
+ }else{
+ /* No more commands, turn off timer interrupt */
+ return 0;
+ }
+}
+
+/*===============================================================
+ * execute_delayed_cmd
+ *
+ * Execute an API command which was passed down from the
+ * sock. Sock is very limited in which commands it can
+ * execute. Wait and No Wait commands are supported.
+ * Place Call, Clear Call and Reset wait commands, where
+ * Accept Call is a no_wait command.
+ *
+ *===============================================================*/
+
+static int execute_delayed_cmd(sdla_t* card, struct net_device *dev,
+ mbox_cmd_t *usr_cmd, char bad_cmd)
+{
+ TX25Mbox* mbox = card->mbox;
+ int err;
+ x25_channel_t *chan = dev->priv;
+ int delay=RETURN_RESULT;
+
+ if (!(*card->u.x.hdlc_buf_status & 0x40) && !bad_cmd){
+ return TRY_CMD_AGAIN;
+ }
+
+ /* This way a command is guaranteed to be executed for
+ * a specific lcn, the network interface is bound to. */
+ usr_cmd->cmd.lcn = chan->common.lcn;
+
+
+ /* If channel is pvc, instead of place call
+ * run x25_channel configuration. If running LAPB HDLC
+ * enable communications.
+ */
+ if ((!chan->common.svc) && (usr_cmd->cmd.command == X25_PLACE_CALL)){
+
+ if (card->u.x.LAPB_hdlc){
+ DBG_PRINTK(KERN_INFO "LAPB: Connecting\n");
+ connect(card);
+ set_chan_state(dev,WAN_CONNECTING);
+ return DELAY_RESULT;
+ }else{
+ DBG_PRINTK(KERN_INFO "%s: PVC is CONNECTING\n",card->devname);
+ if (x25_get_chan_conf(card, chan) == CMD_OK){
+ set_chan_state(dev, WAN_CONNECTED);
+ }else{
+ set_chan_state(dev, WAN_DISCONNECTED);
+ }
+ return RETURN_RESULT;
+ }
+ }
+
+ /* Copy the socket mbox command onto the board */
+
+ memcpy(&mbox->cmd, &usr_cmd->cmd, sizeof(TX25Cmd));
+ if (usr_cmd->cmd.length){
+ memcpy(mbox->data, usr_cmd->data, usr_cmd->cmd.length);
+ }
+
+ /* Check if command is bad. We need to copy the cmd into
+ * the buffer regardless since we return the, mbox to
+ * the user */
+ if (bad_cmd){
+ mbox->cmd.result=0x01;
+ return RETURN_RESULT;
+ }
+
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ if (err != CMD_OK && err != X25RES_NOT_READY)
+ x25_error(card, err, usr_cmd->cmd.command, usr_cmd->cmd.lcn);
+
+ if (mbox->cmd.result == X25RES_NOT_READY){
+ return TRY_CMD_AGAIN;
+ }
+
+ switch (mbox->cmd.command){
+
+ case X25_PLACE_CALL:
+
+ switch (mbox->cmd.result){
+
+ case CMD_OK:
+
+ /* Check if Place call is a wait command or a
+ * no wait command */
+ if (atomic_read(&chan->common.command) & 0x80)
+ delay=RETURN_RESULT;
+ else
+ delay=DELAY_RESULT;
+
+
+ DBG_PRINTK(KERN_INFO "\n%s: PLACE CALL Binding dev %s to lcn %i\n",
+ card->devname,dev->name, mbox->cmd.lcn);
+
+ bind_lcn_to_dev (card, dev, mbox->cmd.lcn);
+ set_chan_state(dev, WAN_CONNECTING);
+ break;
+
+
+ default:
+ delay=RETURN_RESULT;
+ set_chan_state(dev, WAN_DISCONNECTED);
+ break;
+ }
+ break;
+
+ case X25_ACCEPT_CALL:
+
+ switch (mbox->cmd.result){
+
+ case CMD_OK:
+
+ DBG_PRINTK(KERN_INFO "\n%s: ACCEPT Binding dev %s to lcn %i\n",
+ card->devname,dev->name,mbox->cmd.lcn);
+
+ bind_lcn_to_dev (card, dev, mbox->cmd.lcn);
+
+ if (x25_get_chan_conf(card, chan) == CMD_OK){
+
+ set_chan_state(dev, WAN_CONNECTED);
+ delay=RETURN_RESULT;
+
+ }else{
+ if (x25_clear_call(card, usr_cmd->cmd.lcn, 0, 0) == CMD_OK){
+ /* if clear is successful, wait for clear confirm
+ */
+ delay=DELAY_RESULT;
+ }else{
+ /* Do not change the state here. If we fail
+ * the accept the return code is send up
+ *the stack, which will ether retry
+ * or clear the call
+ */
+ DBG_PRINTK(KERN_INFO
+ "%s: ACCEPT: STATE MAY BE CURRUPTED 2 !!!!!\n",
+ card->devname);
+ delay=RETURN_RESULT;
+ }
+ }
+ break;
+
+
+ case X25RES_ASYNC_PACKET:
+ delay=TRY_CMD_AGAIN;
+ break;
+
+ default:
+ DBG_PRINTK(KERN_INFO "%s: ACCEPT FAILED\n",card->devname);
+ if (x25_clear_call(card, usr_cmd->cmd.lcn, 0, 0) == CMD_OK){
+ delay=DELAY_RESULT;
+ }else{
+ /* Do not change the state here. If we fail the accept. The
+ * return code is send up the stack, which will ether retry
+ * or clear the call */
+ DBG_PRINTK(KERN_INFO
+ "%s: ACCEPT: STATE MAY BE CORRUPTED 1 !!!!!\n",
+ card->devname);
+ delay=RETURN_RESULT;
+ }
+ }
+ break;
+
+ case X25_CLEAR_CALL:
+
+ switch (mbox->cmd.result){
+
+ case CMD_OK:
+ DBG_PRINTK(KERN_INFO
+ "CALL CLEAR OK: Dev %s Mbox Lcn %i Chan Lcn %i\n",
+ dev->name,mbox->cmd.lcn,chan->common.lcn);
+ set_chan_state(dev, WAN_DISCONNECTING);
+ delay = DELAY_RESULT;
+ break;
+
+ case X25RES_CHANNEL_IN_USE:
+ case X25RES_ASYNC_PACKET:
+ delay = TRY_CMD_AGAIN;
+ break;
+
+ case X25RES_LINK_NOT_IN_ABM:
+ case X25RES_INVAL_LCN:
+ case X25RES_INVAL_STATE:
+ set_chan_state(dev, WAN_DISCONNECTED);
+ delay = RETURN_RESULT;
+ break;
+
+ default:
+ /* If command did not execute because of user
+ * fault, do not change the state. This will
+ * signal the socket that clear command failed.
+ * User can retry or close the socket.
+ * When socket gets killed, it will set the
+ * chan->disconnect which will signal
+ * driver to clear the call */
+ printk(KERN_INFO "%s: Clear Command Failed, Rc %x\n",
+ card->devname,mbox->cmd.command);
+ delay = RETURN_RESULT;
+ }
+ break;
+ }
+
+ return delay;
+}
+
+/*===============================================================
+ * api_incoming_call
+ *
+ * Pass an incoming call request up the listening
+ * sock. If the API sock is not listening reject the
+ * call.
+ *
+ *===============================================================*/
+
+static int api_incoming_call (sdla_t* card, TX25Mbox *mbox, int lcn)
+{
+ struct sk_buff *skb;
+ int len = sizeof(TX25Cmd)+mbox->cmd.length;
+
+ if (alloc_and_init_skb_buf(card, &skb, len)){
+ printk(KERN_INFO "%s: API incoming call, no memory\n",card->devname);
+ return 1;
+ }
+
+ memcpy(skb_put(skb,len),&mbox->cmd,len);
+
+ skb->mac.raw = skb->data;
+ skb->protocol = htons(X25_PROT);
+ skb->pkt_type = WAN_PACKET_ASYNC;
+
+ if (card->func(skb,card->sk) < 0){
+ printk(KERN_INFO "%s: MAJOR ERROR: Failed to send up place call \n",card->devname);
+ dev_kfree_skb_any(skb);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*===============================================================
+ * send_delayed_cmd_result
+ *
+ * Wait commands like PLEACE CALL or CLEAR CALL must wait
+ * until the result arrives. This function passes
+ * the result to a waiting sock.
+ *
+ *===============================================================*/
+static void send_delayed_cmd_result(sdla_t *card, struct net_device *dev,
+ TX25Mbox* mbox)
+{
+ x25_channel_t *chan = dev->priv;
+ mbox_cmd_t *usr_cmd = (mbox_cmd_t *)chan->common.mbox;
+ struct sk_buff *skb;
+ int len=sizeof(unsigned char);
+
+ atomic_set(&chan->common.command,0);
+
+ /* If the sock is in the process of unlinking the
+ * driver from the socket, we must get out.
+ * This never happends but is a sanity check. */
+ if (test_bit(0,&chan->common.common_critical)){
+ return;
+ }
+
+ if (!usr_cmd || !chan->common.sk || !chan->common.func){
+ DBG_PRINTK(KERN_INFO "Delay result: Sock not bounded sk: %u, func: %u, mbox: %u\n",
+ (unsigned int)chan->common.sk,
+ (unsigned int)chan->common.func,
+ (unsigned int)usr_cmd);
+ return;
+ }
+
+ memcpy(&usr_cmd->cmd, &mbox->cmd, sizeof(TX25Cmd));
+ if (mbox->cmd.length > 0){
+ memcpy(usr_cmd->data, mbox->data, mbox->cmd.length);
+ }
+
+ if (alloc_and_init_skb_buf(card,&skb,len)){
+ printk(KERN_INFO "Delay result: No sock buffers\n");
+ return;
+ }
+
+ memcpy(skb_put(skb,len),&mbox->cmd.command,len);
+
+ skb->mac.raw = skb->data;
+ skb->pkt_type = WAN_PACKET_CMD;
+
+ chan->common.func(skb,dev,chan->common.sk);
+}
+
+/*===============================================================
+ * clear_confirm_event
+ *
+ * Pass the clear confirmation event up the sock. The
+ * API will disconnect only after the clear confirmation
+ * has been received.
+ *
+ * Depending on the state, clear confirmation could
+ * be an OOB event, or a result of an API command.
+ *===============================================================*/
+
+static int clear_confirm_event (sdla_t *card, TX25Mbox* mb)
+{
+ struct net_device *dev;
+ x25_channel_t *chan;
+ unsigned char old_state;
+
+ dev = find_channel(card,mb->cmd.lcn);
+ if (!dev){
+ DBG_PRINTK(KERN_INFO "%s: *** GOT CLEAR BUT NO DEV %i\n",
+ card->devname,mb->cmd.lcn);
+ return 0;
+ }
+
+ chan=dev->priv;
+ DBG_PRINTK(KERN_INFO "%s: GOT CLEAR CONFIRM %s: Mbox lcn %i Chan lcn %i\n",
+ card->devname, dev->name, mb->cmd.lcn, chan->common.lcn);
+
+ /* If not API fall through to default.
+ * If API, send the result to a waiting
+ * socket.
+ */
+
+ old_state = chan->common.state;
+ set_chan_state(dev, WAN_DISCONNECTED);
+
+ if (chan->common.usedby == API){
+ switch (old_state) {
+
+ case WAN_DISCONNECTING:
+ case WAN_CONNECTING:
+ send_delayed_cmd_result(card,dev,mb);
+ break;
+ case WAN_CONNECTED:
+ send_oob_msg(card,dev,mb);
+ break;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+/*===============================================================
+ * send_oob_msg
+ *
+ * Construct an NEM Message and pass it up the connected
+ * sock. If the sock is not bounded discard the NEM.
+ *
+ *===============================================================*/
+
+static void send_oob_msg(sdla_t *card, struct net_device *dev, TX25Mbox *mbox)
+{
+ x25_channel_t *chan = dev->priv;
+ mbox_cmd_t *usr_cmd = (mbox_cmd_t *)chan->common.mbox;
+ struct sk_buff *skb;
+ int len=sizeof(x25api_hdr_t)+mbox->cmd.length;
+ x25api_t *api_hdr;
+
+ /* If the sock is in the process of unlinking the
+ * driver from the socket, we must get out.
+ * This never happends but is a sanity check. */
+ if (test_bit(0,&chan->common.common_critical)){
+ return;
+ }
+
+ if (!usr_cmd || !chan->common.sk || !chan->common.func){
+ DBG_PRINTK(KERN_INFO "OOB MSG: Sock not bounded\n");
+ return;
+ }
+
+ memcpy(&usr_cmd->cmd, &mbox->cmd, sizeof(TX25Cmd));
+ if (mbox->cmd.length > 0){
+ memcpy(usr_cmd->data, mbox->data, mbox->cmd.length);
+ }
+
+ if (alloc_and_init_skb_buf(card,&skb,len)){
+ printk(KERN_INFO "%s: OOB MSG: No sock buffers\n",card->devname);
+ return;
+ }
+
+ api_hdr = (x25api_t*)skb_put(skb,len);
+ api_hdr->hdr.pktType = mbox->cmd.pktType & 0x7F;
+ api_hdr->hdr.qdm = mbox->cmd.qdm;
+ api_hdr->hdr.cause = mbox->cmd.cause;
+ api_hdr->hdr.diagn = mbox->cmd.diagn;
+ api_hdr->hdr.length = mbox->cmd.length;
+ api_hdr->hdr.result = mbox->cmd.result;
+ api_hdr->hdr.lcn = mbox->cmd.lcn;
+
+ if (mbox->cmd.length > 0){
+ memcpy(api_hdr->data,mbox->data,mbox->cmd.length);
+ }
+
+ skb->mac.raw = skb->data;
+ skb->pkt_type = WAN_PACKET_ERR;
+
+ if (chan->common.func(skb,dev,chan->common.sk) < 0){
+ if (bh_enqueue(dev,skb)){
+ printk(KERN_INFO "%s: Dropping OOB MSG\n",card->devname);
+ dev_kfree_skb_any(skb);
+ }
+ }
+
+ DBG_PRINTK(KERN_INFO "%s: OOB MSG OK, %s, lcn %i\n",
+ card->devname, dev->name, mbox->cmd.lcn);
+}
+
+/*===============================================================
+ * alloc_and_init_skb_buf
+ *
+ * Allocate and initialize an skb buffer.
+ *
+ *===============================================================*/
+
+static int alloc_and_init_skb_buf (sdla_t *card, struct sk_buff **skb, int len)
+{
+ struct sk_buff *new_skb = *skb;
+
+ new_skb = dev_alloc_skb(len + X25_HRDHDR_SZ);
+ if (new_skb == NULL){
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ return 1;
+ }
+
+ if (skb_tailroom(new_skb) < len){
+ /* No room for the packet. Call off the whole thing! */
+ dev_kfree_skb_any(new_skb);
+ printk(KERN_INFO "%s: Listen: unexpectedly long packet sequence\n"
+ ,card->devname);
+ *skb = NULL;
+ return 1;
+ }
+
+ *skb = new_skb;
+ return 0;
+
+}
+
+/*===============================================================
+ * api_oob_event
+ *
+ * Send an OOB event up to the sock
+ *
+ *===============================================================*/
+
+static void api_oob_event (sdla_t *card,TX25Mbox *mbox)
+{
+ struct net_device *dev = find_channel(card, mbox->cmd.lcn);
+ x25_channel_t *chan;
+
+ if (!dev)
+ return;
+
+ chan=dev->priv;
+
+ if (chan->common.usedby == API)
+ send_oob_msg(card,dev,mbox);
+
+}
+
+
+
+
+static int channel_disconnect(sdla_t* card, struct net_device *dev)
+{
+
+ int err;
+ x25_channel_t *chan = dev->priv;
+
+ DBG_PRINTK(KERN_INFO "%s: TIMER: %s, Device down disconnecting\n",
+ card->devname,dev->name);
+
+ if (chan->common.svc){
+ err = x25_clear_call(card,chan->common.lcn,0,0);
+ }else{
+ /* If channel is PVC or LAPB HDLC, there is no call
+ * to be cleared, thus drop down to the default
+ * area
+ */
+ err = 1;
+ }
+
+ switch (err){
+
+ case X25RES_CHANNEL_IN_USE:
+ case X25RES_NOT_READY:
+ err = TRY_CMD_AGAIN;
+ break;
+ case CMD_OK:
+ DBG_PRINTK(KERN_INFO "CALL CLEAR OK: Dev %s Chan Lcn %i\n",
+ dev->name,chan->common.lcn);
+
+ set_chan_state(dev,WAN_DISCONNECTING);
+ atomic_set(&chan->common.command,0);
+ err = DELAY_RESULT;
+ break;
+ default:
+ /* If LAPB HDLC protocol, bring the whole link down
+ * once the application terminates
+ */
+
+ set_chan_state(dev,WAN_DISCONNECTED);
+
+ if (card->u.x.LAPB_hdlc){
+ DBG_PRINTK(KERN_INFO "LAPB: Disconnecting Link\n");
+ hdlc_link_down (card);
+ }
+ atomic_set(&chan->common.command,0);
+ err = RETURN_RESULT;
+ break;
+ }
+
+ return err;
+}
+
+static void hdlc_link_down (sdla_t *card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int retry = 5;
+ int err=0;
+
+ do {
+ memset(mbox,0,sizeof(TX25Mbox));
+ mbox->cmd.command = X25_HDLC_LINK_DISC;
+ mbox->cmd.length = 1;
+ mbox->data[0]=0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+
+ } while (err && retry-- && x25_error(card, err, X25_HDLC_LINK_DISC, 0));
+
+ if (err)
+ printk(KERN_INFO "%s: Hdlc Link Down Failed %x\n",card->devname,err);
+
+ disconnect (card);
+
+}
+
+static int check_bad_command(sdla_t* card, struct net_device *dev)
+{
+ x25_channel_t *chan = dev->priv;
+ int bad_cmd = 0;
+
+ switch (atomic_read(&chan->common.command)&0x7F){
+
+ case X25_PLACE_CALL:
+ if (chan->common.state != WAN_DISCONNECTED)
+ bad_cmd=1;
+ break;
+ case X25_CLEAR_CALL:
+ if (chan->common.state == WAN_DISCONNECTED)
+ bad_cmd=1;
+ break;
+ case X25_ACCEPT_CALL:
+ if (chan->common.state != WAN_CONNECTING)
+ bad_cmd=1;
+ break;
+ case X25_RESET:
+ if (chan->common.state != WAN_CONNECTED)
+ bad_cmd=1;
+ break;
+ default:
+ bad_cmd=1;
+ break;
+ }
+
+ if (bad_cmd){
+ printk(KERN_INFO "%s: Invalid State, BAD Command %x, dev %s, lcn %i, st %i\n",
+ card->devname,atomic_read(&chan->common.command),dev->name,
+ chan->common.lcn, chan->common.state);
+ }
+
+ return bad_cmd;
+}
+
+
+
+/*************************** XPIPEMON FUNCTIONS **************************/
+
+/*==============================================================================
+ * Process UDP call of type XPIPE
+ */
+
+static int process_udp_mgmt_pkt(sdla_t *card)
+{
+ int c_retry = MAX_CMD_RETRY;
+ unsigned int len;
+ struct sk_buff *new_skb;
+ TX25Mbox *mbox = card->mbox;
+ int err;
+ int udp_mgmt_req_valid = 1;
+ struct net_device *dev;
+ x25_channel_t *chan;
+ unsigned short lcn;
+ struct timeval tv;
+
+
+ x25_udp_pkt_t *x25_udp_pkt;
+ x25_udp_pkt = (x25_udp_pkt_t *)card->u.x.udp_pkt_data;
+
+ dev = card->u.x.udp_dev;
+ chan = dev->priv;
+ lcn = chan->common.lcn;
+
+ switch(x25_udp_pkt->cblock.command) {
+
+ /* XPIPE_ENABLE_TRACE */
+ case XPIPE_ENABLE_TRACING:
+
+ /* XPIPE_GET_TRACE_INFO */
+ case XPIPE_GET_TRACE_INFO:
+
+ /* SET FT1 MODE */
+ case XPIPE_SET_FT1_MODE:
+
+ if(card->u.x.udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_direction_err;
+ udp_mgmt_req_valid = 0;
+ break;
+ }
+
+ /* XPIPE_FT1_READ_STATUS */
+ case XPIPE_FT1_READ_STATUS:
+
+ /* FT1 MONITOR STATUS */
+ case XPIPE_FT1_STATUS_CTRL:
+ if(card->hw.fwid != SFID_X25_508) {
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_type_err;
+ udp_mgmt_req_valid = 0;
+ break;
+ }
+ default:
+ break;
+ }
+
+ if(!udp_mgmt_req_valid) {
+ /* set length to 0 */
+ x25_udp_pkt->cblock.length = 0;
+ /* set return code */
+ x25_udp_pkt->cblock.result = (card->hw.fwid != SFID_X25_508) ? 0x1F : 0xCD;
+
+ } else {
+
+ switch (x25_udp_pkt->cblock.command) {
+
+
+ case XPIPE_FLUSH_DRIVER_STATS:
+ init_x25_channel_struct(chan);
+ init_global_statistics(card);
+ mbox->cmd.length = 0;
+ break;
+
+
+ case XPIPE_DRIVER_STAT_IFSEND:
+ memcpy(x25_udp_pkt->data, &chan->if_send_stat, sizeof(if_send_stat_t));
+ mbox->cmd.length = sizeof(if_send_stat_t);
+ x25_udp_pkt->cblock.length = mbox->cmd.length;
+ break;
+
+ case XPIPE_DRIVER_STAT_INTR:
+ memcpy(&x25_udp_pkt->data[0], &card->statistics, sizeof(global_stats_t));
+ memcpy(&x25_udp_pkt->data[sizeof(global_stats_t)],
+ &chan->rx_intr_stat, sizeof(rx_intr_stat_t));
+
+ mbox->cmd.length = sizeof(global_stats_t) +
+ sizeof(rx_intr_stat_t);
+ x25_udp_pkt->cblock.length = mbox->cmd.length;
+ break;
+
+ case XPIPE_DRIVER_STAT_GEN:
+ memcpy(x25_udp_pkt->data,
+ &chan->pipe_mgmt_stat.UDP_PIPE_mgmt_kmalloc_err,
+ sizeof(pipe_mgmt_stat_t));
+
+ memcpy(&x25_udp_pkt->data[sizeof(pipe_mgmt_stat_t)],
+ &card->statistics, sizeof(global_stats_t));
+
+ x25_udp_pkt->cblock.result = 0;
+ x25_udp_pkt->cblock.length = sizeof(global_stats_t)+
+ sizeof(rx_intr_stat_t);
+ mbox->cmd.length = x25_udp_pkt->cblock.length;
+ break;
+
+ case XPIPE_ROUTER_UP_TIME:
+ do_gettimeofday(&tv);
+ chan->router_up_time = tv.tv_sec - chan->router_start_time;
+ *(unsigned long *)&x25_udp_pkt->data = chan->router_up_time;
+ x25_udp_pkt->cblock.length = mbox->cmd.length = 4;
+ x25_udp_pkt->cblock.result = 0;
+ break;
+
+ default :
+
+ do {
+ memcpy(&mbox->cmd, &x25_udp_pkt->cblock.command, sizeof(TX25Cmd));
+ if(mbox->cmd.length){
+ memcpy(&mbox->data,
+ (char *)x25_udp_pkt->data,
+ mbox->cmd.length);
+ }
+
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ } while (err && c_retry-- && x25_error(card, err, mbox->cmd.command, 0));
+
+
+ if ( err == CMD_OK ||
+ (err == 1 &&
+ (mbox->cmd.command == 0x06 ||
+ mbox->cmd.command == 0x16) ) ){
+
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_cmnd_OK;
+ } else {
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_cmnd_timeout;
+ }
+
+ /* copy the result back to our buffer */
+ memcpy(&x25_udp_pkt->cblock.command, &mbox->cmd, sizeof(TX25Cmd));
+
+ if(mbox->cmd.length) {
+ memcpy(&x25_udp_pkt->data, &mbox->data, mbox->cmd.length);
+ }
+ break;
+
+ } //switch
+
+ }
+
+ /* Fill UDP TTL */
+
+ x25_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+ len = reply_udp(card->u.x.udp_pkt_data, mbox->cmd.length);
+
+
+ if(card->u.x.udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+ err = x25_send(card, lcn, 0, len, card->u.x.udp_pkt_data);
+ if (!err)
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_send_passed;
+ else
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_adptr_send_failed;
+
+ } else {
+
+ /* Allocate socket buffer */
+ if((new_skb = dev_alloc_skb(len)) != NULL) {
+ void *buf;
+
+ /* copy data into new_skb */
+ buf = skb_put(new_skb, len);
+ memcpy(buf, card->u.x.udp_pkt_data, len);
+
+ /* Decapsulate packet and pass it up the protocol
+ stack */
+ new_skb->dev = dev;
+
+ if (chan->common.usedby == API)
+ new_skb->protocol = htons(X25_PROT);
+ else
+ new_skb->protocol = htons(ETH_P_IP);
+
+ new_skb->mac.raw = new_skb->data;
+
+ netif_rx(new_skb);
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_passed_to_stack;
+
+ } else {
+ ++chan->pipe_mgmt_stat.UDP_PIPE_mgmt_no_socket;
+ printk(KERN_INFO
+ "%s: UDP mgmt cmnd, no socket buffers available!\n",
+ card->devname);
+ }
+ }
+
+ card->u.x.udp_pkt_lgth = 0;
+
+ return 1;
+}
+
+
+/*==============================================================================
+ * Determine what type of UDP call it is. DRVSTATS or XPIPE8ND ?
+ */
+static int udp_pkt_type( struct sk_buff *skb, sdla_t* card )
+{
+ x25_udp_pkt_t *x25_udp_pkt = (x25_udp_pkt_t *)skb->data;
+
+ if((x25_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+ (x25_udp_pkt->ip_pkt.ver_inet_hdr_length == 0x45) &&
+ (x25_udp_pkt->udp_pkt.udp_dst_port == ntohs(card->wandev.udp_port)) &&
+ (x25_udp_pkt->wp_mgmt.request_reply == UDPMGMT_REQUEST)) {
+
+ if(!strncmp(x25_udp_pkt->wp_mgmt.signature,
+ UDPMGMT_XPIPE_SIGNATURE, 8)){
+ return UDP_XPIPE_TYPE;
+ }else{
+ printk(KERN_INFO "%s: UDP Packet, Failed Signature !\n",
+ card->devname);
+ }
+ }
+
+ return UDP_INVALID_TYPE;
+}
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return nothing.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+ unsigned short len, udp_length, temp, ip_length;
+ unsigned long ip_temp;
+ int even_bound = 0;
+
+
+ x25_udp_pkt_t *x25_udp_pkt = (x25_udp_pkt_t *)data;
+
+ /* Set length of packet */
+ len = sizeof(ip_pkt_t)+
+ sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ mbox_len;
+
+
+ /* fill in UDP reply */
+ x25_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+
+ /* fill in UDP length */
+ udp_length = sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ mbox_len;
+
+
+ /* put it on an even boundary */
+ if ( udp_length & 0x0001 ) {
+ udp_length += 1;
+ len += 1;
+ even_bound = 1;
+ }
+
+ temp = (udp_length<<8)|(udp_length>>8);
+ x25_udp_pkt->udp_pkt.udp_length = temp;
+
+ /* swap UDP ports */
+ temp = x25_udp_pkt->udp_pkt.udp_src_port;
+ x25_udp_pkt->udp_pkt.udp_src_port =
+ x25_udp_pkt->udp_pkt.udp_dst_port;
+ x25_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+
+
+ /* add UDP pseudo header */
+ temp = 0x1100;
+ *((unsigned short *)
+ (x25_udp_pkt->data+mbox_len+even_bound)) = temp;
+ temp = (udp_length<<8)|(udp_length>>8);
+ *((unsigned short *)
+ (x25_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+ /* calculate UDP checksum */
+ x25_udp_pkt->udp_pkt.udp_checksum = 0;
+
+ x25_udp_pkt->udp_pkt.udp_checksum =
+ calc_checksum(&data[UDP_OFFSET], udp_length+UDP_OFFSET);
+
+ /* fill in IP length */
+ ip_length = len;
+ temp = (ip_length<<8)|(ip_length>>8);
+ x25_udp_pkt->ip_pkt.total_length = temp;
+
+ /* swap IP addresses */
+ ip_temp = x25_udp_pkt->ip_pkt.ip_src_address;
+ x25_udp_pkt->ip_pkt.ip_src_address =
+ x25_udp_pkt->ip_pkt.ip_dst_address;
+ x25_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+
+ /* fill in IP checksum */
+ x25_udp_pkt->ip_pkt.hdr_checksum = 0;
+ x25_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data, sizeof(ip_pkt_t));
+
+ return len;
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+ unsigned short temp;
+ unsigned long sum=0;
+ int i;
+
+ for( i = 0; i <len; i+=2 ) {
+ memcpy(&temp,&data[i],2);
+ sum += (unsigned long)temp;
+ }
+
+ while (sum >> 16 ) {
+ sum = (sum & 0xffffUL) + (sum >> 16);
+ }
+
+ temp = (unsigned short)sum;
+ temp = ~temp;
+
+ if( temp == 0 )
+ temp = 0xffff;
+
+ return temp;
+}
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(int udp_type, char udp_pkt_src, sdla_t* card,
+ struct net_device *dev, struct sk_buff *skb,
+ int lcn)
+{
+ int udp_pkt_stored = 0;
+
+ if(!card->u.x.udp_pkt_lgth && (skb->len <= MAX_LGTH_UDP_MGNT_PKT)){
+ card->u.x.udp_pkt_lgth = skb->len;
+ card->u.x.udp_type = udp_type;
+ card->u.x.udp_pkt_src = udp_pkt_src;
+ card->u.x.udp_lcn = lcn;
+ card->u.x.udp_dev = dev;
+ memcpy(card->u.x.udp_pkt_data, skb->data, skb->len);
+ card->u.x.timer_int_enabled |= TMR_INT_ENABLED_UDP_PKT;
+ udp_pkt_stored = 1;
+
+ }else{
+ printk(KERN_INFO "%s: ERROR: UDP packet not stored for LCN %d\n",
+ card->devname,lcn);
+ }
+
+ if(udp_pkt_src == UDP_PKT_FRM_STACK){
+ dev_kfree_skb_any(skb);
+ }else{
+ dev_kfree_skb_any(skb);
+ }
+
+ return(udp_pkt_stored);
+}
+
+
+
+/*=============================================================================
+ * Initial the ppp_private_area structure.
+ */
+static void init_x25_channel_struct( x25_channel_t *chan )
+{
+ memset(&chan->if_send_stat.if_send_entry,0,sizeof(if_send_stat_t));
+ memset(&chan->rx_intr_stat.rx_intr_no_socket,0,sizeof(rx_intr_stat_t));
+ memset(&chan->pipe_mgmt_stat.UDP_PIPE_mgmt_kmalloc_err,0,sizeof(pipe_mgmt_stat_t));
+}
+
+/*============================================================================
+ * Initialize Global Statistics
+ */
+static void init_global_statistics( sdla_t *card )
+{
+ memset(&card->statistics.isr_entry,0,sizeof(global_stats_t));
+}
+
+
+/*===============================================================
+ * SMP Support
+ * ==============================================================*/
+
+static void S508_S514_lock(sdla_t *card, unsigned long *smp_flags)
+{
+ spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+}
+static void S508_S514_unlock(sdla_t *card, unsigned long *smp_flags)
+{
+ spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+/*===============================================================
+ * x25_timer_routine
+ *
+ * A more efficient polling routine. Each half a second
+ * queue a polling task. We want to do the polling in a
+ * task not timer, because timer runs in interrupt time.
+ *
+ * FIXME Polling should be rethinked.
+ *==============================================================*/
+
+static void x25_timer_routine(unsigned long data)
+{
+ sdla_t *card = (sdla_t*)data;
+
+ if (!card->wandev.dev){
+ printk(KERN_INFO "%s: Stopping the X25 Poll Timer: No Dev.\n",
+ card->devname);
+ return;
+ }
+
+ if (card->open_cnt != card->u.x.num_of_ch){
+ printk(KERN_INFO "%s: Stopping the X25 Poll Timer: Interface down.\n",
+ card->devname);
+ return;
+ }
+
+ if (test_bit(PERI_CRIT,&card->wandev.critical)){
+ printk(KERN_INFO "%s: Stopping the X25 Poll Timer: Shutting down.\n",
+ card->devname);
+ return;
+ }
+
+ if (!test_and_set_bit(POLL_CRIT,&card->wandev.critical)){
+ trigger_x25_poll(card);
+ }
+
+ card->u.x.x25_timer.expires=jiffies+(HZ>>1);
+ add_timer(&card->u.x.x25_timer);
+ return;
+}
+
+void disable_comm_shutdown(sdla_t *card)
+{
+ TX25Mbox* mbox = card->mbox;
+ int err;
+
+ /* Turn of interrutps */
+ mbox->data[0] = 0;
+ if (card->hw.fwid == SFID_X25_508){
+ mbox->data[1] = card->hw.irq;
+ mbox->data[2] = 2;
+ mbox->cmd.length = 3;
+ }else {
+ mbox->cmd.length = 1;
+ }
+ mbox->cmd.command = X25_SET_INTERRUPT_MODE;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err)
+ printk(KERN_INFO "INTERRUPT OFF FAIED %x\n",err);
+
+ /* Bring down HDLC */
+ mbox->cmd.command = X25_HDLC_LINK_CLOSE;
+ mbox->cmd.length = 0;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err)
+ printk(KERN_INFO "LINK CLOSED FAILED %x\n",err);
+
+
+ /* Brind down DTR */
+ mbox->data[0] = 0;
+ mbox->data[2] = 0;
+ mbox->data[1] = 0x01;
+ mbox->cmd.length = 3;
+ mbox->cmd.command = X25_SET_GLOBAL_VARS;
+ err = sdla_exec(mbox) ? mbox->cmd.result : CMD_TIMEOUT;
+ if (err)
+ printk(KERN_INFO "DTR DOWN FAILED %x\n",err);
+
+}
+
+MODULE_LICENSE("GPL");
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdladrv.c b/drivers/net/wan/sdladrv.c
new file mode 100644
index 00000000000..c8bc6da57a4
--- /dev/null
+++ b/drivers/net/wan/sdladrv.c
@@ -0,0 +1,2318 @@
+/*****************************************************************************
+* sdladrv.c SDLA Support Module. Main module.
+*
+* This module is a library of common hardware-specific functions
+* used by all Sangoma drivers.
+*
+* Author: Gideon Hack
+*
+* Copyright: (c) 1995-2000 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Mar 20, 2001 Nenad Corbic Added the auto_pci_cfg filed, to support
+* the PCISLOT #0.
+* Apr 04, 2000 Nenad Corbic Fixed the auto memory detection code.
+* The memory test at address 0xC8000.
+* Mar 09, 2000 Nenad Corbic Added Gideon's Bug Fix: clear pci
+* interrupt flags on initial load.
+* Jun 02, 1999 Gideon Hack Added support for the S514 adapter.
+* Updates for Linux 2.2.X kernels.
+* Sep 17, 1998 Jaspreet Singh Updates for linux 2.2.X kernels
+* Dec 20, 1996 Gene Kozin Version 3.0.0. Complete overhaul.
+* Jul 12, 1996 Gene Kozin Changes for Linux 2.0 compatibility.
+* Jun 12, 1996 Gene Kozin Added support for S503 card.
+* Apr 30, 1996 Gene Kozin SDLA hardware interrupt is acknowledged before
+* calling protocolspecific ISR.
+* Register I/O ports with Linux kernel.
+* Miscellaneous bug fixes.
+* Dec 20, 1995 Gene Kozin Fixed a bug in interrupt routine.
+* Oct 14, 1995 Gene Kozin Initial version.
+*****************************************************************************/
+
+/*****************************************************************************
+ * Notes:
+ * ------
+ * 1. This code is ment to be system-independent (as much as possible). To
+ * achive this, various macros are used to hide system-specific interfaces.
+ * To compile this code, one of the following constants must be defined:
+ *
+ * Platform Define
+ * -------- ------
+ * Linux _LINUX_
+ * SCO Unix _SCO_UNIX_
+ *
+ * 2. Supported adapter types:
+ *
+ * S502A
+ * ES502A (S502E)
+ * S503
+ * S507
+ * S508 (S509)
+ *
+ * 3. S502A Notes:
+ *
+ * There is no separate DPM window enable/disable control in S502A. It
+ * opens immediately after a window number it written to the HMCR
+ * register. To close the window, HMCR has to be written a value
+ * ????1111b (e.g. 0x0F or 0xFF).
+ *
+ * S502A DPM window cannot be located at offset E000 (e.g. 0xAE000).
+ *
+ * There should be a delay of ??? before reading back S502A status
+ * register.
+ *
+ * 4. S502E Notes:
+ *
+ * S502E has a h/w bug: although default IRQ line state is HIGH, enabling
+ * interrupts by setting bit 1 of the control register (BASE) to '1'
+ * causes it to go LOW! Therefore, disabling interrupts by setting that
+ * bit to '0' causes low-to-high transition on IRQ line (ghosty
+ * interrupt). The same occurs when disabling CPU by resetting bit 0 of
+ * CPU control register (BASE+3) - see the next note.
+ *
+ * S502E CPU and DPM control is limited:
+ *
+ * o CPU cannot be stopped independently. Resetting bit 0 of the CPUi
+ * control register (BASE+3) shuts the board down entirely, including
+ * DPM;
+ *
+ * o DPM access cannot be controlled dynamically. Ones CPU is started,
+ * bit 1 of the control register (BASE) is used to enable/disable IRQ,
+ * so that access to shared memory cannot be disabled while CPU is
+ * running.
+ ****************************************************************************/
+
+#define _LINUX_
+
+#if defined(_LINUX_) /****** Linux *******************************/
+
+#include <linux/config.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/module.h> /* support for loadable modules */
+#include <linux/jiffies.h> /* for jiffies, HZ, etc. */
+#include <linux/sdladrv.h> /* API definitions */
+#include <linux/sdlasfm.h> /* SDLA firmware module definitions */
+#include <linux/sdlapci.h> /* SDLA PCI hardware definitions */
+#include <linux/pci.h> /* PCI defines and function prototypes */
+#include <asm/io.h> /* for inb(), outb(), etc. */
+
+#define _INB(port) (inb(port))
+#define _OUTB(port, byte) (outb((byte),(port)))
+#define SYSTEM_TICK jiffies
+
+#include <linux/init.h>
+
+
+#elif defined(_SCO_UNIX_) /****** SCO Unix ****************************/
+
+#if !defined(INKERNEL)
+#error This code MUST be compiled in kernel mode!
+#endif
+#include <sys/sdladrv.h> /* API definitions */
+#include <sys/sdlasfm.h> /* SDLA firmware module definitions */
+#include <sys/inline.h> /* for inb(), outb(), etc. */
+#define _INB(port) (inb(port))
+#define _OUTB(port, byte) (outb((port),(byte)))
+#define SYSTEM_TICK lbolt
+
+#else
+#error Unknown system type!
+#endif
+
+#define MOD_VERSION 3
+#define MOD_RELEASE 0
+
+#define SDLA_IODELAY 100 /* I/O Rd/Wr delay, 10 works for 486DX2-66 */
+#define EXEC_DELAY 20 /* shared memory access delay, mks */
+#define EXEC_TIMEOUT (HZ*2) /* command timeout, in ticks */
+
+/* I/O port address range */
+#define S502A_IORANGE 3
+#define S502E_IORANGE 4
+#define S503_IORANGE 3
+#define S507_IORANGE 4
+#define S508_IORANGE 4
+
+/* Maximum amount of memory */
+#define S502_MAXMEM 0x10000L
+#define S503_MAXMEM 0x10000L
+#define S507_MAXMEM 0x40000L
+#define S508_MAXMEM 0x40000L
+
+/* Minimum amount of memory */
+#define S502_MINMEM 0x8000L
+#define S503_MINMEM 0x8000L
+#define S507_MINMEM 0x20000L
+#define S508_MINMEM 0x20000L
+#define NO_PORT -1
+
+
+
+
+
+/****** Function Prototypes *************************************************/
+
+/* Hardware-specific functions */
+static int sdla_detect (sdlahw_t* hw);
+static int sdla_autodpm (sdlahw_t* hw);
+static int sdla_setdpm (sdlahw_t* hw);
+static int sdla_load (sdlahw_t* hw, sfm_t* sfm, unsigned len);
+static int sdla_init (sdlahw_t* hw);
+static unsigned long sdla_memtest (sdlahw_t* hw);
+static int sdla_bootcfg (sdlahw_t* hw, sfm_info_t* sfminfo);
+static unsigned char make_config_byte (sdlahw_t* hw);
+static int sdla_start (sdlahw_t* hw, unsigned addr);
+
+static int init_s502a (sdlahw_t* hw);
+static int init_s502e (sdlahw_t* hw);
+static int init_s503 (sdlahw_t* hw);
+static int init_s507 (sdlahw_t* hw);
+static int init_s508 (sdlahw_t* hw);
+
+static int detect_s502a (int port);
+static int detect_s502e (int port);
+static int detect_s503 (int port);
+static int detect_s507 (int port);
+static int detect_s508 (int port);
+static int detect_s514 (sdlahw_t* hw);
+static int find_s514_adapter(sdlahw_t* hw, char find_first_S514_card);
+
+/* Miscellaneous functions */
+static void peek_by_4 (unsigned long src, void* buf, unsigned len);
+static void poke_by_4 (unsigned long dest, void* buf, unsigned len);
+static int calibrate_delay (int mks);
+static int get_option_index (unsigned* optlist, unsigned optval);
+static unsigned check_memregion (void* ptr, unsigned len);
+static unsigned test_memregion (void* ptr, unsigned len);
+static unsigned short checksum (unsigned char* buf, unsigned len);
+static int init_pci_slot(sdlahw_t *);
+
+static int pci_probe(sdlahw_t *hw);
+
+/****** Global Data **********************************************************
+ * Note: All data must be explicitly initialized!!!
+ */
+
+static struct pci_device_id sdladrv_pci_tbl[] = {
+ { V3_VENDOR_ID, V3_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(pci, sdladrv_pci_tbl);
+
+MODULE_LICENSE("GPL");
+
+/* private data */
+static char modname[] = "sdladrv";
+static char fullname[] = "SDLA Support Module";
+static char copyright[] = "(c) 1995-1999 Sangoma Technologies Inc.";
+static unsigned exec_idle;
+
+/* Hardware configuration options.
+ * These are arrays of configuration options used by verification routines.
+ * The first element of each array is its size (i.e. number of options).
+ */
+static unsigned s502_port_options[] =
+ { 4, 0x250, 0x300, 0x350, 0x360 }
+;
+static unsigned s503_port_options[] =
+ { 8, 0x250, 0x254, 0x300, 0x304, 0x350, 0x354, 0x360, 0x364 }
+;
+static unsigned s508_port_options[] =
+ { 8, 0x250, 0x270, 0x280, 0x300, 0x350, 0x360, 0x380, 0x390 }
+;
+
+static unsigned s502a_irq_options[] = { 0 };
+static unsigned s502e_irq_options[] = { 4, 2, 3, 5, 7 };
+static unsigned s503_irq_options[] = { 5, 2, 3, 4, 5, 7 };
+static unsigned s508_irq_options[] = { 8, 3, 4, 5, 7, 10, 11, 12, 15 };
+
+static unsigned s502a_dpmbase_options[] =
+{
+ 28,
+ 0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000,
+ 0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000,
+ 0xD0000, 0xD2000, 0xD4000, 0xD6000, 0xD8000, 0xDA000, 0xDC000,
+ 0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000,
+};
+static unsigned s507_dpmbase_options[] =
+{
+ 32,
+ 0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000, 0xAE000,
+ 0xB0000, 0xB2000, 0xB4000, 0xB6000, 0xB8000, 0xBA000, 0xBC000, 0xBE000,
+ 0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000, 0xCE000,
+ 0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000, 0xEE000,
+};
+static unsigned s508_dpmbase_options[] = /* incl. S502E and S503 */
+{
+ 32,
+ 0xA0000, 0xA2000, 0xA4000, 0xA6000, 0xA8000, 0xAA000, 0xAC000, 0xAE000,
+ 0xC0000, 0xC2000, 0xC4000, 0xC6000, 0xC8000, 0xCA000, 0xCC000, 0xCE000,
+ 0xD0000, 0xD2000, 0xD4000, 0xD6000, 0xD8000, 0xDA000, 0xDC000, 0xDE000,
+ 0xE0000, 0xE2000, 0xE4000, 0xE6000, 0xE8000, 0xEA000, 0xEC000, 0xEE000,
+};
+
+/*
+static unsigned s502_dpmsize_options[] = { 2, 0x2000, 0x10000 };
+static unsigned s507_dpmsize_options[] = { 2, 0x2000, 0x4000 };
+static unsigned s508_dpmsize_options[] = { 1, 0x2000 };
+*/
+
+static unsigned s502a_pclk_options[] = { 2, 3600, 7200 };
+static unsigned s502e_pclk_options[] = { 5, 3600, 5000, 7200, 8000, 10000 };
+static unsigned s503_pclk_options[] = { 3, 7200, 8000, 10000 };
+static unsigned s507_pclk_options[] = { 1, 12288 };
+static unsigned s508_pclk_options[] = { 1, 16000 };
+
+/* Host memory control register masks */
+static unsigned char s502a_hmcr[] =
+{
+ 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, /* A0000 - AC000 */
+ 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, /* C0000 - CC000 */
+ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, /* D0000 - DC000 */
+ 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, /* E0000 - EC000 */
+};
+static unsigned char s502e_hmcr[] =
+{
+ 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, /* A0000 - AE000 */
+ 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, /* C0000 - CE000 */
+ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, /* D0000 - DE000 */
+ 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, /* E0000 - EE000 */
+};
+static unsigned char s507_hmcr[] =
+{
+ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, /* A0000 - AE000 */
+ 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, /* B0000 - BE000 */
+ 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, /* C0000 - CE000 */
+ 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, /* E0000 - EE000 */
+};
+static unsigned char s508_hmcr[] =
+{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* A0000 - AE000 */
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* C0000 - CE000 */
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* D0000 - DE000 */
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, /* E0000 - EE000 */
+};
+
+static unsigned char s507_irqmask[] =
+{
+ 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0
+};
+
+static int pci_slot_ar[MAX_S514_CARDS];
+
+/******* Kernel Loadable Module Entry Points ********************************/
+
+/*============================================================================
+ * Module 'insert' entry point.
+ * o print announcement
+ * o initialize static data
+ * o calibrate SDLA shared memory access delay.
+ *
+ * Return: 0 Ok
+ * < 0 error.
+ * Context: process
+ */
+
+static int __init sdladrv_init(void)
+{
+ int i=0;
+
+ printk(KERN_INFO "%s v%u.%u %s\n",
+ fullname, MOD_VERSION, MOD_RELEASE, copyright);
+ exec_idle = calibrate_delay(EXEC_DELAY);
+#ifdef WANDEBUG
+ printk(KERN_DEBUG "%s: exec_idle = %d\n", modname, exec_idle);
+#endif
+
+ /* Initialize the PCI Card array, which
+ * will store flags, used to mark
+ * card initialization state */
+ for (i=0; i<MAX_S514_CARDS; i++)
+ pci_slot_ar[i] = 0xFF;
+
+ return 0;
+}
+
+/*============================================================================
+ * Module 'remove' entry point.
+ * o release all remaining system resources
+ */
+static void __exit sdladrv_cleanup(void)
+{
+}
+
+module_init(sdladrv_init);
+module_exit(sdladrv_cleanup);
+
+/******* Kernel APIs ********************************************************/
+
+/*============================================================================
+ * Set up adapter.
+ * o detect adapter type
+ * o verify hardware configuration options
+ * o check for hardware conflicts
+ * o set up adapter shared memory
+ * o test adapter memory
+ * o load firmware
+ * Return: 0 ok.
+ * < 0 error
+ */
+
+EXPORT_SYMBOL(sdla_setup);
+
+int sdla_setup (sdlahw_t* hw, void* sfm, unsigned len)
+{
+ unsigned* irq_opt = NULL; /* IRQ options */
+ unsigned* dpmbase_opt = NULL; /* DPM window base options */
+ unsigned* pclk_opt = NULL; /* CPU clock rate options */
+ int err=0;
+
+ if (sdla_detect(hw)) {
+ if(hw->type != SDLA_S514)
+ printk(KERN_INFO "%s: no SDLA card found at port 0x%X\n",
+ modname, hw->port);
+ return -EINVAL;
+ }
+
+ if(hw->type != SDLA_S514) {
+ printk(KERN_INFO "%s: found S%04u card at port 0x%X.\n",
+ modname, hw->type, hw->port);
+
+ hw->dpmsize = SDLA_WINDOWSIZE;
+ switch (hw->type) {
+ case SDLA_S502A:
+ hw->io_range = S502A_IORANGE;
+ irq_opt = s502a_irq_options;
+ dpmbase_opt = s502a_dpmbase_options;
+ pclk_opt = s502a_pclk_options;
+ break;
+
+ case SDLA_S502E:
+ hw->io_range = S502E_IORANGE;
+ irq_opt = s502e_irq_options;
+ dpmbase_opt = s508_dpmbase_options;
+ pclk_opt = s502e_pclk_options;
+ break;
+
+ case SDLA_S503:
+ hw->io_range = S503_IORANGE;
+ irq_opt = s503_irq_options;
+ dpmbase_opt = s508_dpmbase_options;
+ pclk_opt = s503_pclk_options;
+ break;
+
+ case SDLA_S507:
+ hw->io_range = S507_IORANGE;
+ irq_opt = s508_irq_options;
+ dpmbase_opt = s507_dpmbase_options;
+ pclk_opt = s507_pclk_options;
+ break;
+
+ case SDLA_S508:
+ hw->io_range = S508_IORANGE;
+ irq_opt = s508_irq_options;
+ dpmbase_opt = s508_dpmbase_options;
+ pclk_opt = s508_pclk_options;
+ break;
+ }
+
+ /* Verify IRQ configuration options */
+ if (!get_option_index(irq_opt, hw->irq)) {
+ printk(KERN_INFO "%s: IRQ %d is invalid!\n",
+ modname, hw->irq);
+ return -EINVAL;
+ }
+
+ /* Verify CPU clock rate configuration options */
+ if (hw->pclk == 0)
+ hw->pclk = pclk_opt[1]; /* use default */
+
+ else if (!get_option_index(pclk_opt, hw->pclk)) {
+ printk(KERN_INFO "%s: CPU clock %u is invalid!\n",
+ modname, hw->pclk);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "%s: assuming CPU clock rate of %u kHz.\n",
+ modname, hw->pclk);
+
+ /* Setup adapter dual-port memory window and test memory */
+ if (hw->dpmbase == 0) {
+ err = sdla_autodpm(hw);
+ if (err) {
+ printk(KERN_INFO
+ "%s: can't find available memory region!\n",
+ modname);
+ return err;
+ }
+ }
+ else if (!get_option_index(dpmbase_opt,
+ virt_to_phys(hw->dpmbase))) {
+ printk(KERN_INFO
+ "%s: memory address 0x%lX is invalid!\n",
+ modname, virt_to_phys(hw->dpmbase));
+ return -EINVAL;
+ }
+ else if (sdla_setdpm(hw)) {
+ printk(KERN_INFO
+ "%s: 8K memory region at 0x%lX is not available!\n",
+ modname, virt_to_phys(hw->dpmbase));
+ return -EINVAL;
+ }
+ printk(KERN_INFO
+ "%s: dual-port memory window is set at 0x%lX.\n",
+ modname, virt_to_phys(hw->dpmbase));
+
+
+ /* If we find memory in 0xE**** Memory region,
+ * warn the user to disable the SHADOW RAM.
+ * Since memory corruption can occur if SHADOW is
+ * enabled. This can causes random crashes ! */
+ if (virt_to_phys(hw->dpmbase) >= 0xE0000){
+ printk(KERN_WARNING "\n%s: !!!!!!!! WARNING !!!!!!!!\n",modname);
+ printk(KERN_WARNING "%s: WANPIPE is using 0x%lX memory region !!!\n",
+ modname, virt_to_phys(hw->dpmbase));
+ printk(KERN_WARNING " Please disable the SHADOW RAM, otherwise\n");
+ printk(KERN_WARNING " your system might crash randomly from time to time !\n");
+ printk(KERN_WARNING "%s: !!!!!!!! WARNING !!!!!!!!\n\n",modname);
+ }
+ }
+
+ else {
+ hw->memory = test_memregion((void*)hw->dpmbase,
+ MAX_SIZEOF_S514_MEMORY);
+ if(hw->memory < (256 * 1024)) {
+ printk(KERN_INFO
+ "%s: error in testing S514 memory (0x%lX)\n",
+ modname, hw->memory);
+ sdla_down(hw);
+ return -EINVAL;
+ }
+ }
+
+ printk(KERN_INFO "%s: found %luK bytes of on-board memory\n",
+ modname, hw->memory / 1024);
+
+ /* Load firmware. If loader fails then shut down adapter */
+ err = sdla_load(hw, sfm, len);
+ if (err) sdla_down(hw); /* shutdown adapter */
+
+ return err;
+}
+
+/*============================================================================
+ * Shut down SDLA: disable shared memory access and interrupts, stop CPU, etc.
+ */
+
+EXPORT_SYMBOL(sdla_down);
+
+int sdla_down (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int i;
+ unsigned char CPU_no;
+ u32 int_config, int_status;
+
+ if(!port && (hw->type != SDLA_S514))
+ return -EFAULT;
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ _OUTB(port, 0x08); /* halt CPU */
+ _OUTB(port, 0x08);
+ _OUTB(port, 0x08);
+ hw->regs[0] = 0x08;
+ _OUTB(port + 1, 0xFF); /* close memory window */
+ hw->regs[1] = 0xFF;
+ break;
+
+ case SDLA_S502E:
+ _OUTB(port + 3, 0); /* stop CPU */
+ _OUTB(port, 0); /* reset board */
+ for (i = 0; i < S502E_IORANGE; ++i)
+ hw->regs[i] = 0
+ ;
+ break;
+
+ case SDLA_S503:
+ case SDLA_S507:
+ case SDLA_S508:
+ _OUTB(port, 0); /* reset board logic */
+ hw->regs[0] = 0;
+ break;
+
+ case SDLA_S514:
+ /* halt the adapter */
+ *(char *)hw->vector = S514_CPU_HALT;
+ CPU_no = hw->S514_cpu_no[0];
+
+ /* disable the PCI IRQ and disable memory access */
+ pci_read_config_dword(hw->pci_dev, PCI_INT_CONFIG, &int_config);
+ int_config &= (CPU_no == S514_CPU_A) ? ~PCI_DISABLE_IRQ_CPU_A : ~PCI_DISABLE_IRQ_CPU_B;
+ pci_write_config_dword(hw->pci_dev, PCI_INT_CONFIG, int_config);
+ read_S514_int_stat(hw, &int_status);
+ S514_intack(hw, int_status);
+ if(CPU_no == S514_CPU_A)
+ pci_write_config_dword(hw->pci_dev, PCI_MAP0_DWORD,
+ PCI_CPU_A_MEM_DISABLE);
+ else
+ pci_write_config_dword(hw->pci_dev, PCI_MAP1_DWORD,
+ PCI_CPU_B_MEM_DISABLE);
+
+ /* free up the allocated virtual memory */
+ iounmap((void *)hw->dpmbase);
+ iounmap((void *)hw->vector);
+ break;
+
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Map shared memory window into SDLA address space.
+ */
+
+EXPORT_SYMBOL(sdla_mapmem);
+
+int sdla_mapmem (sdlahw_t* hw, unsigned long addr)
+{
+ unsigned port = hw->port;
+ register int tmp;
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ case SDLA_S502E:
+ if (addr < S502_MAXMEM) { /* verify parameter */
+ tmp = addr >> 13; /* convert to register mask */
+ _OUTB(port + 2, tmp);
+ hw->regs[2] = tmp;
+ }
+ else return -EINVAL;
+ break;
+
+ case SDLA_S503:
+ if (addr < S503_MAXMEM) { /* verify parameter */
+ tmp = (hw->regs[0] & 0x8F) | ((addr >> 9) & 0x70);
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp;
+ }
+ else return -EINVAL;
+ break;
+
+ case SDLA_S507:
+ if (addr < S507_MAXMEM) {
+ if (!(_INB(port) & 0x02))
+ return -EIO;
+ tmp = addr >> 13; /* convert to register mask */
+ _OUTB(port + 2, tmp);
+ hw->regs[2] = tmp;
+ }
+ else return -EINVAL;
+ break;
+
+ case SDLA_S508:
+ if (addr < S508_MAXMEM) {
+ tmp = addr >> 13; /* convert to register mask */
+ _OUTB(port + 2, tmp);
+ hw->regs[2] = tmp;
+ }
+ else return -EINVAL;
+ break;
+
+ case SDLA_S514:
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+ hw->vector = addr & 0xFFFFE000L;
+ return 0;
+}
+
+/*============================================================================
+ * Enable interrupt generation.
+ */
+
+EXPORT_SYMBOL(sdla_inten);
+
+int sdla_inten (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ switch (hw->type) {
+ case SDLA_S502E:
+ /* Note thar interrupt control operations on S502E are allowed
+ * only if CPU is enabled (bit 0 of status register is set).
+ */
+ if (_INB(port) & 0x01) {
+ _OUTB(port, 0x02); /* bit1 = 1, bit2 = 0 */
+ _OUTB(port, 0x06); /* bit1 = 1, bit2 = 1 */
+ hw->regs[0] = 0x06;
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S503:
+ tmp = hw->regs[0] | 0x04;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (!(_INB(port) & 0x02)) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S508:
+ tmp = hw->regs[0] | 0x10;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (!(_INB(port + 1) & 0x10)) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S502A:
+ case SDLA_S507:
+ break;
+
+ case SDLA_S514:
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Disable interrupt generation.
+ */
+
+EXPORT_SYMBOL(sdla_intde);
+
+int sdla_intde (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ switch (hw->type) {
+ case SDLA_S502E:
+ /* Notes:
+ * 1) interrupt control operations are allowed only if CPU is
+ * enabled (bit 0 of status register is set).
+ * 2) disabling interrupts using bit 1 of control register
+ * causes IRQ line go high, therefore we are going to use
+ * 0x04 instead: lower it to inhibit interrupts to PC.
+ */
+ if (_INB(port) & 0x01) {
+ _OUTB(port, hw->regs[0] & ~0x04);
+ hw->regs[0] &= ~0x04;
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S503:
+ tmp = hw->regs[0] & ~0x04;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) & 0x02) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S508:
+ tmp = hw->regs[0] & ~0x10;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) & 0x10) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S502A:
+ case SDLA_S507:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Acknowledge SDLA hardware interrupt.
+ */
+
+EXPORT_SYMBOL(sdla_intack);
+
+int sdla_intack (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp;
+
+ switch (hw->type) {
+ case SDLA_S502E:
+ /* To acknoledge hardware interrupt we have to toggle bit 3 of
+ * control register: \_/
+ * Note that interrupt control operations on S502E are allowed
+ * only if CPU is enabled (bit 1 of status register is set).
+ */
+ if (_INB(port) & 0x01) {
+ tmp = hw->regs[0] & ~0x04;
+ _OUTB(port, tmp);
+ tmp |= 0x04;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp;
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S503:
+ if (_INB(port) & 0x04) {
+ tmp = hw->regs[0] & ~0x08;
+ _OUTB(port, tmp);
+ tmp |= 0x08;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp;
+ }
+ break;
+
+ case SDLA_S502A:
+ case SDLA_S507:
+ case SDLA_S508:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+/*============================================================================
+ * Acknowledge S514 hardware interrupt.
+ */
+
+EXPORT_SYMBOL(S514_intack);
+
+void S514_intack (sdlahw_t* hw, u32 int_status)
+{
+ pci_write_config_dword(hw->pci_dev, PCI_INT_STATUS, int_status);
+}
+
+
+/*============================================================================
+ * Read the S514 hardware interrupt status.
+ */
+
+EXPORT_SYMBOL(read_S514_int_stat);
+
+void read_S514_int_stat (sdlahw_t* hw, u32* int_status)
+{
+ pci_read_config_dword(hw->pci_dev, PCI_INT_STATUS, int_status);
+}
+
+
+/*============================================================================
+ * Generate an interrupt to adapter's CPU.
+ */
+
+EXPORT_SYMBOL(sdla_intr);
+
+int sdla_intr (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ if (!(_INB(port) & 0x40)) {
+ _OUTB(port, 0x10); /* issue NMI to CPU */
+ hw->regs[0] = 0x10;
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S507:
+ if ((_INB(port) & 0x06) == 0x06) {
+ _OUTB(port + 3, 0);
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S508:
+ if (_INB(port + 1) & 0x02) {
+ _OUTB(port, 0x08);
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S502E:
+ case SDLA_S503:
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Execute Adapter Command.
+ * o Set exec flag.
+ * o Busy-wait until flag is reset.
+ * o Return number of loops made, or 0 if command timed out.
+ */
+
+EXPORT_SYMBOL(sdla_exec);
+
+int sdla_exec (void* opflag)
+{
+ volatile unsigned char* flag = opflag;
+ unsigned long tstop;
+ int nloops;
+
+ if(readb(flag) != 0x00) {
+ printk(KERN_INFO
+ "WANPIPE: opp flag set on entry to sdla_exec\n");
+ return 0;
+ }
+
+ writeb(0x01, flag);
+
+ tstop = SYSTEM_TICK + EXEC_TIMEOUT;
+
+ for (nloops = 1; (readb(flag) == 0x01); ++ nloops) {
+ unsigned delay = exec_idle;
+ while (-- delay); /* delay */
+ if (SYSTEM_TICK > tstop) return 0; /* time is up! */
+ }
+ return nloops;
+}
+
+/*============================================================================
+ * Read absolute adapter memory.
+ * Transfer data from adapter's memory to data buffer.
+ *
+ * Note:
+ * Care should be taken when crossing dual-port memory window boundary.
+ * This function is not atomic, so caller must disable interrupt if
+ * interrupt routines are accessing adapter shared memory.
+ */
+
+EXPORT_SYMBOL(sdla_peek);
+
+int sdla_peek (sdlahw_t* hw, unsigned long addr, void* buf, unsigned len)
+{
+
+ if (addr + len > hw->memory) /* verify arguments */
+ return -EINVAL;
+
+ if(hw->type == SDLA_S514) { /* copy data for the S514 adapter */
+ peek_by_4 ((unsigned long)hw->dpmbase + addr, buf, len);
+ return 0;
+ }
+
+ else { /* copy data for the S508 adapter */
+ unsigned long oldvec = hw->vector;
+ unsigned winsize = hw->dpmsize;
+ unsigned curpos, curlen; /* current offset and block size */
+ unsigned long curvec; /* current DPM window vector */
+ int err = 0;
+
+ while (len && !err) {
+ curpos = addr % winsize; /* current window offset */
+ curvec = addr - curpos; /* current window vector */
+ curlen = (len > (winsize - curpos)) ?
+ (winsize - curpos) : len;
+ /* Relocate window and copy block of data */
+ err = sdla_mapmem(hw, curvec);
+ peek_by_4 ((unsigned long)hw->dpmbase + curpos, buf,
+ curlen);
+ addr += curlen;
+ buf = (char*)buf + curlen;
+ len -= curlen;
+ }
+
+ /* Restore DPM window position */
+ sdla_mapmem(hw, oldvec);
+ return err;
+ }
+}
+
+
+/*============================================================================
+ * Read data from adapter's memory to a data buffer in 4-byte chunks.
+ * Note that we ensure that the SDLA memory address is on a 4-byte boundary
+ * before we begin moving the data in 4-byte chunks.
+*/
+
+static void peek_by_4 (unsigned long src, void* buf, unsigned len)
+{
+
+ /* byte copy data until we get to a 4-byte boundary */
+ while (len && (src & 0x03)) {
+ *(char *)buf ++ = readb(src ++);
+ len --;
+ }
+
+ /* copy data in 4-byte chunks */
+ while (len >= 4) {
+ *(unsigned long *)buf = readl(src);
+ buf += 4;
+ src += 4;
+ len -= 4;
+ }
+
+ /* byte copy any remaining data */
+ while (len) {
+ *(char *)buf ++ = readb(src ++);
+ len --;
+ }
+}
+
+
+/*============================================================================
+ * Write Absolute Adapter Memory.
+ * Transfer data from data buffer to adapter's memory.
+ *
+ * Note:
+ * Care should be taken when crossing dual-port memory window boundary.
+ * This function is not atomic, so caller must disable interrupt if
+ * interrupt routines are accessing adapter shared memory.
+ */
+
+EXPORT_SYMBOL(sdla_poke);
+
+int sdla_poke (sdlahw_t* hw, unsigned long addr, void* buf, unsigned len)
+{
+
+ if (addr + len > hw->memory) /* verify arguments */
+ return -EINVAL;
+
+ if(hw->type == SDLA_S514) { /* copy data for the S514 adapter */
+ poke_by_4 ((unsigned long)hw->dpmbase + addr, buf, len);
+ return 0;
+ }
+
+ else { /* copy data for the S508 adapter */
+ unsigned long oldvec = hw->vector;
+ unsigned winsize = hw->dpmsize;
+ unsigned curpos, curlen; /* current offset and block size */
+ unsigned long curvec; /* current DPM window vector */
+ int err = 0;
+
+ while (len && !err) {
+ curpos = addr % winsize; /* current window offset */
+ curvec = addr - curpos; /* current window vector */
+ curlen = (len > (winsize - curpos)) ?
+ (winsize - curpos) : len;
+ /* Relocate window and copy block of data */
+ sdla_mapmem(hw, curvec);
+ poke_by_4 ((unsigned long)hw->dpmbase + curpos, buf,
+ curlen);
+ addr += curlen;
+ buf = (char*)buf + curlen;
+ len -= curlen;
+ }
+
+ /* Restore DPM window position */
+ sdla_mapmem(hw, oldvec);
+ return err;
+ }
+}
+
+
+/*============================================================================
+ * Write from a data buffer to adapter's memory in 4-byte chunks.
+ * Note that we ensure that the SDLA memory address is on a 4-byte boundary
+ * before we begin moving the data in 4-byte chunks.
+*/
+
+static void poke_by_4 (unsigned long dest, void* buf, unsigned len)
+{
+
+ /* byte copy data until we get to a 4-byte boundary */
+ while (len && (dest & 0x03)) {
+ writeb (*(char *)buf ++, dest ++);
+ len --;
+ }
+
+ /* copy data in 4-byte chunks */
+ while (len >= 4) {
+ writel (*(unsigned long *)buf, dest);
+ dest += 4;
+ buf += 4;
+ len -= 4;
+ }
+
+ /* byte copy any remaining data */
+ while (len) {
+ writeb (*(char *)buf ++ , dest ++);
+ len --;
+ }
+}
+
+
+#ifdef DONT_COMPIPLE_THIS
+#endif /* DONT_COMPIPLE_THIS */
+
+/****** Hardware-Specific Functions *****************************************/
+
+/*============================================================================
+ * Detect adapter type.
+ * o if adapter type is specified then call detection routine for that adapter
+ * type. Otherwise call detection routines for every adapter types until
+ * adapter is detected.
+ *
+ * Notes:
+ * 1) Detection tests are destructive! Adapter will be left in shutdown state
+ * after the test.
+ */
+static int sdla_detect (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int err = 0;
+
+ if (!port && (hw->type != SDLA_S514))
+ return -EFAULT;
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ if (!detect_s502a(port)) err = -ENODEV;
+ break;
+
+ case SDLA_S502E:
+ if (!detect_s502e(port)) err = -ENODEV;
+ break;
+
+ case SDLA_S503:
+ if (!detect_s503(port)) err = -ENODEV;
+ break;
+
+ case SDLA_S507:
+ if (!detect_s507(port)) err = -ENODEV;
+ break;
+
+ case SDLA_S508:
+ if (!detect_s508(port)) err = -ENODEV;
+ break;
+
+ case SDLA_S514:
+ if (!detect_s514(hw)) err = -ENODEV;
+ break;
+
+ default:
+ if (detect_s502a(port))
+ hw->type = SDLA_S502A;
+ else if (detect_s502e(port))
+ hw->type = SDLA_S502E;
+ else if (detect_s503(port))
+ hw->type = SDLA_S503;
+ else if (detect_s507(port))
+ hw->type = SDLA_S507;
+ else if (detect_s508(port))
+ hw->type = SDLA_S508;
+ else err = -ENODEV;
+ }
+ return err;
+}
+
+/*============================================================================
+ * Autoselect memory region.
+ * o try all available DMP address options from the top down until success.
+ */
+static int sdla_autodpm (sdlahw_t* hw)
+{
+ int i, err = -EINVAL;
+ unsigned* opt;
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ opt = s502a_dpmbase_options;
+ break;
+
+ case SDLA_S502E:
+ case SDLA_S503:
+ case SDLA_S508:
+ opt = s508_dpmbase_options;
+ break;
+
+ case SDLA_S507:
+ opt = s507_dpmbase_options;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Start testing from 8th position, address
+ * 0xC8000 from the 508 address table.
+ * We don't want to test A**** addresses, since
+ * they are usually used for Video */
+ for (i = 8; i <= opt[0] && err; i++) {
+ hw->dpmbase = phys_to_virt(opt[i]);
+ err = sdla_setdpm(hw);
+ }
+ return err;
+}
+
+/*============================================================================
+ * Set up adapter dual-port memory window.
+ * o shut down adapter
+ * o make sure that no physical memory exists in this region, i.e entire
+ * region reads 0xFF and is not writable when adapter is shut down.
+ * o initialize adapter hardware
+ * o make sure that region is usable with SDLA card, i.e. we can write to it
+ * when adapter is configured.
+ */
+static int sdla_setdpm (sdlahw_t* hw)
+{
+ int err;
+
+ /* Shut down card and verify memory region */
+ sdla_down(hw);
+ if (check_memregion(hw->dpmbase, hw->dpmsize))
+ return -EINVAL;
+
+ /* Initialize adapter and test on-board memory segment by segment.
+ * If memory size appears to be less than shared memory window size,
+ * assume that memory region is unusable.
+ */
+ err = sdla_init(hw);
+ if (err) return err;
+
+ if (sdla_memtest(hw) < hw->dpmsize) { /* less than window size */
+ sdla_down(hw);
+ return -EIO;
+ }
+ sdla_mapmem(hw, 0L); /* set window vector at bottom */
+ return 0;
+}
+
+/*============================================================================
+ * Load adapter from the memory image of the SDLA firmware module.
+ * o verify firmware integrity and compatibility
+ * o start adapter up
+ */
+static int sdla_load (sdlahw_t* hw, sfm_t* sfm, unsigned len)
+{
+
+ int i;
+
+ /* Verify firmware signature */
+ if (strcmp(sfm->signature, SFM_SIGNATURE)) {
+ printk(KERN_INFO "%s: not SDLA firmware!\n",
+ modname);
+ return -EINVAL;
+ }
+
+ /* Verify firmware module format version */
+ if (sfm->version != SFM_VERSION) {
+ printk(KERN_INFO
+ "%s: firmware format %u rejected! Expecting %u.\n",
+ modname, sfm->version, SFM_VERSION);
+ return -EINVAL;
+ }
+
+ /* Verify firmware module length and checksum */
+ if ((len - offsetof(sfm_t, image) != sfm->info.codesize) ||
+ (checksum((void*)&sfm->info,
+ sizeof(sfm_info_t) + sfm->info.codesize) != sfm->checksum)) {
+ printk(KERN_INFO "%s: firmware corrupted!\n", modname);
+ return -EINVAL;
+ }
+
+ /* Announce */
+ printk(KERN_INFO "%s: loading %s (ID=%u)...\n", modname,
+ (sfm->descr[0] != '\0') ? sfm->descr : "unknown firmware",
+ sfm->info.codeid);
+
+ if(hw->type == SDLA_S514)
+ printk(KERN_INFO "%s: loading S514 adapter, CPU %c\n",
+ modname, hw->S514_cpu_no[0]);
+
+ /* Scan through the list of compatible adapters and make sure our
+ * adapter type is listed.
+ */
+ for (i = 0;
+ (i < SFM_MAX_SDLA) && (sfm->info.adapter[i] != hw->type);
+ ++i);
+
+ if (i == SFM_MAX_SDLA) {
+ printk(KERN_INFO "%s: firmware is not compatible with S%u!\n",
+ modname, hw->type);
+ return -EINVAL;
+ }
+
+
+ /* Make sure there is enough on-board memory */
+ if (hw->memory < sfm->info.memsize) {
+ printk(KERN_INFO
+ "%s: firmware needs %lu bytes of on-board memory!\n",
+ modname, sfm->info.memsize);
+ return -EINVAL;
+ }
+
+ /* Move code onto adapter */
+ if (sdla_poke(hw, sfm->info.codeoffs, sfm->image, sfm->info.codesize)) {
+ printk(KERN_INFO "%s: failed to load code segment!\n",
+ modname);
+ return -EIO;
+ }
+
+ /* Prepare boot-time configuration data and kick-off CPU */
+ sdla_bootcfg(hw, &sfm->info);
+ if (sdla_start(hw, sfm->info.startoffs)) {
+ printk(KERN_INFO "%s: Damn... Adapter won't start!\n",
+ modname);
+ return -EIO;
+ }
+
+ /* position DPM window over the mailbox and enable interrupts */
+ if (sdla_mapmem(hw, sfm->info.winoffs) || sdla_inten(hw)) {
+ printk(KERN_INFO "%s: adapter hardware failure!\n",
+ modname);
+ return -EIO;
+ }
+ hw->fwid = sfm->info.codeid; /* set firmware ID */
+ return 0;
+}
+
+/*============================================================================
+ * Initialize SDLA hardware: setup memory window, IRQ, etc.
+ */
+static int sdla_init (sdlahw_t* hw)
+{
+ int i;
+
+ for (i = 0; i < SDLA_MAXIORANGE; ++i)
+ hw->regs[i] = 0;
+
+ switch (hw->type) {
+ case SDLA_S502A: return init_s502a(hw);
+ case SDLA_S502E: return init_s502e(hw);
+ case SDLA_S503: return init_s503(hw);
+ case SDLA_S507: return init_s507(hw);
+ case SDLA_S508: return init_s508(hw);
+ }
+ return -EINVAL;
+}
+
+/*============================================================================
+ * Test adapter on-board memory.
+ * o slide DPM window from the bottom up and test adapter memory segment by
+ * segment.
+ * Return adapter memory size.
+ */
+static unsigned long sdla_memtest (sdlahw_t* hw)
+{
+ unsigned long memsize;
+ unsigned winsize;
+
+ for (memsize = 0, winsize = hw->dpmsize;
+ !sdla_mapmem(hw, memsize) &&
+ (test_memregion(hw->dpmbase, winsize) == winsize)
+ ;
+ memsize += winsize)
+ ;
+ hw->memory = memsize;
+ return memsize;
+}
+
+/*============================================================================
+ * Prepare boot-time firmware configuration data.
+ * o position DPM window
+ * o initialize configuration data area
+ */
+static int sdla_bootcfg (sdlahw_t* hw, sfm_info_t* sfminfo)
+{
+ unsigned char* data;
+
+ if (!sfminfo->datasize) return 0; /* nothing to do */
+
+ if (sdla_mapmem(hw, sfminfo->dataoffs) != 0)
+ return -EIO;
+
+ if(hw->type == SDLA_S514)
+ data = (void*)(hw->dpmbase + sfminfo->dataoffs);
+ else
+ data = (void*)((u8 *)hw->dpmbase +
+ (sfminfo->dataoffs - hw->vector));
+
+ memset_io (data, 0, sfminfo->datasize);
+
+ writeb (make_config_byte(hw), &data[0x00]);
+
+ switch (sfminfo->codeid) {
+ case SFID_X25_502:
+ case SFID_X25_508:
+ writeb (3, &data[0x01]); /* T1 timer */
+ writeb (10, &data[0x03]); /* N2 */
+ writeb (7, &data[0x06]); /* HDLC window size */
+ writeb (1, &data[0x0B]); /* DTE */
+ writeb (2, &data[0x0C]); /* X.25 packet window size */
+ writew (128, &data[0x0D]); /* default X.25 data size */
+ writew (128, &data[0x0F]); /* maximum X.25 data size */
+ break;
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Prepare configuration byte identifying adapter type and CPU clock rate.
+ */
+static unsigned char make_config_byte (sdlahw_t* hw)
+{
+ unsigned char byte = 0;
+
+ switch (hw->pclk) {
+ case 5000: byte = 0x01; break;
+ case 7200: byte = 0x02; break;
+ case 8000: byte = 0x03; break;
+ case 10000: byte = 0x04; break;
+ case 16000: byte = 0x05; break;
+ }
+
+ switch (hw->type) {
+ case SDLA_S502E: byte |= 0x80; break;
+ case SDLA_S503: byte |= 0x40; break;
+ }
+ return byte;
+}
+
+/*============================================================================
+ * Start adapter's CPU.
+ * o calculate a pointer to adapter's cold boot entry point
+ * o position DPM window
+ * o place boot instruction (jp addr) at cold boot entry point
+ * o start CPU
+ */
+static int sdla_start (sdlahw_t* hw, unsigned addr)
+{
+ unsigned port = hw->port;
+ unsigned char *bootp;
+ int err, tmp, i;
+
+ if (!port && (hw->type != SDLA_S514)) return -EFAULT;
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ bootp = hw->dpmbase;
+ bootp += 0x66;
+ break;
+
+ case SDLA_S502E:
+ case SDLA_S503:
+ case SDLA_S507:
+ case SDLA_S508:
+ case SDLA_S514:
+ bootp = hw->dpmbase;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ err = sdla_mapmem(hw, 0);
+ if (err) return err;
+
+ writeb (0xC3, bootp); /* Z80: 'jp' opcode */
+ bootp ++;
+ writew (addr, bootp);
+
+ switch (hw->type) {
+ case SDLA_S502A:
+ _OUTB(port, 0x10); /* issue NMI to CPU */
+ hw->regs[0] = 0x10;
+ break;
+
+ case SDLA_S502E:
+ _OUTB(port + 3, 0x01); /* start CPU */
+ hw->regs[3] = 0x01;
+ for (i = 0; i < SDLA_IODELAY; ++i);
+ if (_INB(port) & 0x01) { /* verify */
+ /*
+ * Enabling CPU changes functionality of the
+ * control register, so we have to reset its
+ * mirror.
+ */
+ _OUTB(port, 0); /* disable interrupts */
+ hw->regs[0] = 0;
+ }
+ else return -EIO;
+ break;
+
+ case SDLA_S503:
+ tmp = hw->regs[0] | 0x09; /* set bits 0 and 3 */
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i);
+ if (!(_INB(port) & 0x01)) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S507:
+ tmp = hw->regs[0] | 0x02;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i);
+ if (!(_INB(port) & 0x04)) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S508:
+ tmp = hw->regs[0] | 0x02;
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i);
+ if (!(_INB(port + 1) & 0x02)) /* verify */
+ return -EIO;
+ break;
+
+ case SDLA_S514:
+ writeb (S514_CPU_START, hw->vector);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*============================================================================
+ * Initialize S502A adapter.
+ */
+static int init_s502a (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ if (!detect_s502a(port))
+ return -ENODEV;
+
+ hw->regs[0] = 0x08;
+ hw->regs[1] = 0xFF;
+
+ /* Verify configuration options */
+ i = get_option_index(s502a_dpmbase_options, virt_to_phys(hw->dpmbase));
+ if (i == 0)
+ return -EINVAL;
+
+ tmp = s502a_hmcr[i - 1];
+ switch (hw->dpmsize) {
+ case 0x2000:
+ tmp |= 0x01;
+ break;
+
+ case 0x10000L:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Setup dual-port memory window (this also enables memory access) */
+ _OUTB(port + 1, tmp);
+ hw->regs[1] = tmp;
+ hw->regs[0] = 0x08;
+ return 0;
+}
+
+/*============================================================================
+ * Initialize S502E adapter.
+ */
+static int init_s502e (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ if (!detect_s502e(port))
+ return -ENODEV;
+
+ /* Verify configuration options */
+ i = get_option_index(s508_dpmbase_options, virt_to_phys(hw->dpmbase));
+ if (i == 0)
+ return -EINVAL;
+
+ tmp = s502e_hmcr[i - 1];
+ switch (hw->dpmsize) {
+ case 0x2000:
+ tmp |= 0x01;
+ break;
+
+ case 0x10000L:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Setup dual-port memory window */
+ _OUTB(port + 1, tmp);
+ hw->regs[1] = tmp;
+
+ /* Enable memory access */
+ _OUTB(port, 0x02);
+ hw->regs[0] = 0x02;
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ return (_INB(port) & 0x02) ? 0 : -EIO;
+}
+
+/*============================================================================
+ * Initialize S503 adapter.
+ * ---------------------------------------------------------------------------
+ */
+static int init_s503 (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ if (!detect_s503(port))
+ return -ENODEV;
+
+ /* Verify configuration options */
+ i = get_option_index(s508_dpmbase_options, virt_to_phys(hw->dpmbase));
+ if (i == 0)
+ return -EINVAL;
+
+ tmp = s502e_hmcr[i - 1];
+ switch (hw->dpmsize) {
+ case 0x2000:
+ tmp |= 0x01;
+ break;
+
+ case 0x10000L:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Setup dual-port memory window */
+ _OUTB(port + 1, tmp);
+ hw->regs[1] = tmp;
+
+ /* Enable memory access */
+ _OUTB(port, 0x02);
+ hw->regs[0] = 0x02; /* update mirror */
+ return 0;
+}
+
+/*============================================================================
+ * Initialize S507 adapter.
+ */
+static int init_s507 (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ if (!detect_s507(port))
+ return -ENODEV;
+
+ /* Verify configuration options */
+ i = get_option_index(s507_dpmbase_options, virt_to_phys(hw->dpmbase));
+ if (i == 0)
+ return -EINVAL;
+
+ tmp = s507_hmcr[i - 1];
+ switch (hw->dpmsize) {
+ case 0x2000:
+ tmp |= 0x01;
+ break;
+
+ case 0x10000L:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Enable adapter's logic */
+ _OUTB(port, 0x01);
+ hw->regs[0] = 0x01;
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (!(_INB(port) & 0x20))
+ return -EIO;
+
+ /* Setup dual-port memory window */
+ _OUTB(port + 1, tmp);
+ hw->regs[1] = tmp;
+
+ /* Enable memory access */
+ tmp = hw->regs[0] | 0x04;
+ if (hw->irq) {
+ i = get_option_index(s508_irq_options, hw->irq);
+ if (i) tmp |= s507_irqmask[i - 1];
+ }
+ _OUTB(port, tmp);
+ hw->regs[0] = tmp; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ return (_INB(port) & 0x08) ? 0 : -EIO;
+}
+
+/*============================================================================
+ * Initialize S508 adapter.
+ */
+static int init_s508 (sdlahw_t* hw)
+{
+ unsigned port = hw->port;
+ int tmp, i;
+
+ if (!detect_s508(port))
+ return -ENODEV;
+
+ /* Verify configuration options */
+ i = get_option_index(s508_dpmbase_options, virt_to_phys(hw->dpmbase));
+ if (i == 0)
+ return -EINVAL;
+
+ /* Setup memory configuration */
+ tmp = s508_hmcr[i - 1];
+ _OUTB(port + 1, tmp);
+ hw->regs[1] = tmp;
+
+ /* Enable memory access */
+ _OUTB(port, 0x04);
+ hw->regs[0] = 0x04; /* update mirror */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ return (_INB(port + 1) & 0x04) ? 0 : -EIO;
+}
+
+/*============================================================================
+ * Detect S502A adapter.
+ * Following tests are used to detect S502A adapter:
+ * 1. All registers other than status (BASE) should read 0xFF
+ * 2. After writing 00001000b to control register, status register should
+ * read 01000000b.
+ * 3. After writing 0 to control register, status register should still
+ * read 01000000b.
+ * 4. After writing 00000100b to control register, status register should
+ * read 01000100b.
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test.
+ */
+static int detect_s502a (int port)
+{
+ int i, j;
+
+ if (!get_option_index(s502_port_options, port))
+ return 0;
+
+ for (j = 1; j < SDLA_MAXIORANGE; ++j) {
+ if (_INB(port + j) != 0xFF)
+ return 0;
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ }
+
+ _OUTB(port, 0x08); /* halt CPU */
+ _OUTB(port, 0x08);
+ _OUTB(port, 0x08);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0x40)
+ return 0;
+ _OUTB(port, 0x00);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0x40)
+ return 0;
+ _OUTB(port, 0x04);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0x44)
+ return 0;
+
+ /* Reset adapter */
+ _OUTB(port, 0x08);
+ _OUTB(port, 0x08);
+ _OUTB(port, 0x08);
+ _OUTB(port + 1, 0xFF);
+ return 1;
+}
+
+/*============================================================================
+ * Detect S502E adapter.
+ * Following tests are used to verify adapter presence:
+ * 1. All registers other than status (BASE) should read 0xFF.
+ * 2. After writing 0 to CPU control register (BASE+3), status register
+ * (BASE) should read 11111000b.
+ * 3. After writing 00000100b to port BASE (set bit 2), status register
+ * (BASE) should read 11111100b.
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test.
+ */
+static int detect_s502e (int port)
+{
+ int i, j;
+
+ if (!get_option_index(s502_port_options, port))
+ return 0;
+ for (j = 1; j < SDLA_MAXIORANGE; ++j) {
+ if (_INB(port + j) != 0xFF)
+ return 0;
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ }
+
+ _OUTB(port + 3, 0); /* CPU control reg. */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0xF8) /* read status */
+ return 0;
+ _OUTB(port, 0x04); /* set bit 2 */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0xFC) /* verify */
+ return 0;
+
+ /* Reset adapter */
+ _OUTB(port, 0);
+ return 1;
+}
+
+/*============================================================================
+ * Detect s503 adapter.
+ * Following tests are used to verify adapter presence:
+ * 1. All registers other than status (BASE) should read 0xFF.
+ * 2. After writing 0 to control register (BASE), status register (BASE)
+ * should read 11110000b.
+ * 3. After writing 00000100b (set bit 2) to control register (BASE),
+ * status register should read 11110010b.
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test.
+ */
+static int detect_s503 (int port)
+{
+ int i, j;
+
+ if (!get_option_index(s503_port_options, port))
+ return 0;
+ for (j = 1; j < SDLA_MAXIORANGE; ++j) {
+ if (_INB(port + j) != 0xFF)
+ return 0;
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ }
+
+ _OUTB(port, 0); /* reset control reg.*/
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0xF0) /* read status */
+ return 0;
+ _OUTB(port, 0x04); /* set bit 2 */
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if (_INB(port) != 0xF2) /* verify */
+ return 0;
+
+ /* Reset adapter */
+ _OUTB(port, 0);
+ return 1;
+}
+
+/*============================================================================
+ * Detect s507 adapter.
+ * Following tests are used to detect s507 adapter:
+ * 1. All ports should read the same value.
+ * 2. After writing 0x00 to control register, status register should read
+ * ?011000?b.
+ * 3. After writing 0x01 to control register, status register should read
+ * ?011001?b.
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test.
+ */
+static int detect_s507 (int port)
+{
+ int tmp, i, j;
+
+ if (!get_option_index(s508_port_options, port))
+ return 0;
+ tmp = _INB(port);
+ for (j = 1; j < S507_IORANGE; ++j) {
+ if (_INB(port + j) != tmp)
+ return 0;
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ }
+
+ _OUTB(port, 0x00);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if ((_INB(port) & 0x7E) != 0x30)
+ return 0;
+ _OUTB(port, 0x01);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if ((_INB(port) & 0x7E) != 0x32)
+ return 0;
+
+ /* Reset adapter */
+ _OUTB(port, 0x00);
+ return 1;
+}
+
+/*============================================================================
+ * Detect s508 adapter.
+ * Following tests are used to detect s508 adapter:
+ * 1. After writing 0x00 to control register, status register should read
+ * ??000000b.
+ * 2. After writing 0x10 to control register, status register should read
+ * ??010000b
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test.
+ */
+static int detect_s508 (int port)
+{
+ int i;
+
+ if (!get_option_index(s508_port_options, port))
+ return 0;
+ _OUTB(port, 0x00);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if ((_INB(port + 1) & 0x3F) != 0x00)
+ return 0;
+ _OUTB(port, 0x10);
+ for (i = 0; i < SDLA_IODELAY; ++i); /* delay */
+ if ((_INB(port + 1) & 0x3F) != 0x10)
+ return 0;
+
+ /* Reset adapter */
+ _OUTB(port, 0x00);
+ return 1;
+}
+
+/*============================================================================
+ * Detect s514 PCI adapter.
+ * Return 1 if detected o.k. or 0 if failed.
+ * Note: This test is destructive! Adapter will be left in shutdown
+ * state after the test.
+ */
+static int detect_s514 (sdlahw_t* hw)
+{
+ unsigned char CPU_no, slot_no, auto_slot_cfg;
+ int number_S514_cards = 0;
+ u32 S514_mem_base_addr = 0;
+ u32 ut_u32;
+ struct pci_dev *pci_dev;
+
+
+#ifndef CONFIG_PCI
+ printk(KERN_INFO "%s: Linux not compiled for PCI usage!\n", modname);
+ return 0;
+#endif
+
+ /*
+ The 'setup()' procedure in 'sdlamain.c' passes the CPU number and the
+ slot number defined in 'router.conf' via the 'port' definition.
+ */
+ CPU_no = hw->S514_cpu_no[0];
+ slot_no = hw->S514_slot_no;
+ auto_slot_cfg = hw->auto_pci_cfg;
+
+ if (auto_slot_cfg){
+ printk(KERN_INFO "%s: srch... S514 card, CPU %c, Slot=Auto\n",
+ modname, CPU_no);
+
+ }else{
+ printk(KERN_INFO "%s: srch... S514 card, CPU %c, Slot #%d\n",
+ modname, CPU_no, slot_no);
+ }
+
+ /* check to see that CPU A or B has been selected in 'router.conf' */
+ switch(CPU_no) {
+ case S514_CPU_A:
+ case S514_CPU_B:
+ break;
+
+ default:
+ printk(KERN_INFO "%s: S514 CPU definition invalid.\n",
+ modname);
+ printk(KERN_INFO "Must be 'A' or 'B'\n");
+ return 0;
+ }
+
+ number_S514_cards = find_s514_adapter(hw, 0);
+ if(!number_S514_cards)
+ return 0;
+
+ /* we are using a single S514 adapter with a slot of 0 so re-read the */
+ /* location of this adapter */
+ if((number_S514_cards == 1) && auto_slot_cfg) {
+ number_S514_cards = find_s514_adapter(hw, 1);
+ if(!number_S514_cards) {
+ printk(KERN_INFO "%s: Error finding PCI card\n",
+ modname);
+ return 0;
+ }
+ }
+
+ pci_dev = hw->pci_dev;
+ /* read the physical memory base address */
+ S514_mem_base_addr = (CPU_no == S514_CPU_A) ?
+ (pci_dev->resource[1].start) :
+ (pci_dev->resource[2].start);
+
+ printk(KERN_INFO "%s: S514 PCI memory at 0x%X\n",
+ modname, S514_mem_base_addr);
+ if(!S514_mem_base_addr) {
+ if(CPU_no == S514_CPU_B)
+ printk(KERN_INFO "%s: CPU #B not present on the card\n", modname);
+ else
+ printk(KERN_INFO "%s: No PCI memory allocated to card\n", modname);
+ return 0;
+ }
+
+ /* enable the PCI memory */
+ pci_read_config_dword(pci_dev,
+ (CPU_no == S514_CPU_A) ? PCI_MAP0_DWORD : PCI_MAP1_DWORD,
+ &ut_u32);
+ pci_write_config_dword(pci_dev,
+ (CPU_no == S514_CPU_A) ? PCI_MAP0_DWORD : PCI_MAP1_DWORD,
+ (ut_u32 | PCI_MEMORY_ENABLE));
+
+ /* check the IRQ allocated and enable IRQ usage */
+ if(!(hw->irq = pci_dev->irq)) {
+ printk(KERN_INFO "%s: IRQ not allocated to S514 adapter\n",
+ modname);
+ return 0;
+ }
+
+ /* BUG FIX : Mar 6 2000
+ * On a initial loading of the card, we must check
+ * and clear PCI interrupt bits, due to a reset
+ * problem on some other boards. i.e. An interrupt
+ * might be pending, even after system bootup,
+ * in which case, when starting wanrouter the machine
+ * would crash.
+ */
+ if (init_pci_slot(hw))
+ return 0;
+
+ pci_read_config_dword(pci_dev, PCI_INT_CONFIG, &ut_u32);
+ ut_u32 |= (CPU_no == S514_CPU_A) ?
+ PCI_ENABLE_IRQ_CPU_A : PCI_ENABLE_IRQ_CPU_B;
+ pci_write_config_dword(pci_dev, PCI_INT_CONFIG, ut_u32);
+
+ printk(KERN_INFO "%s: IRQ %d allocated to the S514 card\n",
+ modname, hw->irq);
+
+ /* map the physical PCI memory to virtual memory */
+ (void *)hw->dpmbase = ioremap((unsigned long)S514_mem_base_addr,
+ (unsigned long)MAX_SIZEOF_S514_MEMORY);
+ /* map the physical control register memory to virtual memory */
+ hw->vector = (unsigned long)ioremap(
+ (unsigned long)(S514_mem_base_addr + S514_CTRL_REG_BYTE),
+ (unsigned long)16);
+
+ if(!hw->dpmbase || !hw->vector) {
+ printk(KERN_INFO "%s: PCI virtual memory allocation failed\n",
+ modname);
+ return 0;
+ }
+
+ /* halt the adapter */
+ writeb (S514_CPU_HALT, hw->vector);
+
+ return 1;
+}
+
+/*============================================================================
+ * Find the S514 PCI adapter in the PCI bus.
+ * Return the number of S514 adapters found (0 if no adapter found).
+ */
+static int find_s514_adapter(sdlahw_t* hw, char find_first_S514_card)
+{
+ unsigned char slot_no;
+ int number_S514_cards = 0;
+ char S514_found_in_slot = 0;
+ u16 PCI_subsys_vendor;
+
+ struct pci_dev *pci_dev = NULL;
+
+ slot_no = hw->S514_slot_no;
+
+ while ((pci_dev = pci_find_device(V3_VENDOR_ID, V3_DEVICE_ID, pci_dev))
+ != NULL) {
+
+ pci_read_config_word(pci_dev, PCI_SUBSYS_VENDOR_WORD,
+ &PCI_subsys_vendor);
+
+ if(PCI_subsys_vendor != SANGOMA_SUBSYS_VENDOR)
+ continue;
+
+ hw->pci_dev = pci_dev;
+
+ if(find_first_S514_card)
+ return(1);
+
+ number_S514_cards ++;
+
+ printk(KERN_INFO
+ "%s: S514 card found, slot #%d (devfn 0x%X)\n",
+ modname, ((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK),
+ pci_dev->devfn);
+
+ if (hw->auto_pci_cfg){
+ hw->S514_slot_no = ((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK);
+ slot_no = hw->S514_slot_no;
+
+ }else if (((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK) == slot_no){
+ S514_found_in_slot = 1;
+ break;
+ }
+ }
+
+ /* if no S514 adapter has been found, then exit */
+ if (!number_S514_cards) {
+ printk(KERN_INFO "%s: Error, no S514 adapters found\n", modname);
+ return 0;
+ }
+ /* if more than one S514 card has been found, then the user must have */ /* defined a slot number so that the correct adapter is used */
+ else if ((number_S514_cards > 1) && hw->auto_pci_cfg) {
+ printk(KERN_INFO "%s: Error, PCI Slot autodetect Failed! \n"
+ "%s: More than one S514 adapter found.\n"
+ "%s: Disable the Autodetect feature and supply\n"
+ "%s: the PCISLOT numbers for each card.\n",
+ modname,modname,modname,modname);
+ return 0;
+ }
+ /* if the user has specified a slot number and the S514 adapter has */
+ /* not been found in that slot, then exit */
+ else if (!hw->auto_pci_cfg && !S514_found_in_slot) {
+ printk(KERN_INFO
+ "%s: Error, S514 card not found in specified slot #%d\n",
+ modname, slot_no);
+ return 0;
+ }
+
+ return (number_S514_cards);
+}
+
+
+
+/******* Miscellaneous ******************************************************/
+
+/*============================================================================
+ * Calibrate SDLA memory access delay.
+ * Count number of idle loops made within 1 second and then calculate the
+ * number of loops that should be made to achive desired delay.
+ */
+static int calibrate_delay (int mks)
+{
+ unsigned int delay;
+ unsigned long stop;
+
+ for (delay = 0, stop = SYSTEM_TICK + HZ; SYSTEM_TICK < stop; ++delay);
+ return (delay/(1000000L/mks) + 1);
+}
+
+/*============================================================================
+ * Get option's index into the options list.
+ * Return option's index (1 .. N) or zero if option is invalid.
+ */
+static int get_option_index (unsigned* optlist, unsigned optval)
+{
+ int i;
+
+ for (i = 1; i <= optlist[0]; ++i)
+ if ( optlist[i] == optval)
+ return i;
+ return 0;
+}
+
+/*============================================================================
+ * Check memory region to see if it's available.
+ * Return: 0 ok.
+ */
+static unsigned check_memregion (void* ptr, unsigned len)
+{
+ volatile unsigned char* p = ptr;
+
+ for (; len && (readb (p) == 0xFF); --len, ++p) {
+ writeb (0, p); /* attempt to write 0 */
+ if (readb(p) != 0xFF) { /* still has to read 0xFF */
+ writeb (0xFF, p);/* restore original value */
+ break; /* not good */
+ }
+ }
+
+ return len;
+}
+
+/*============================================================================
+ * Test memory region.
+ * Return: size of the region that passed the test.
+ * Note: Region size must be multiple of 2 !
+ */
+static unsigned test_memregion (void* ptr, unsigned len)
+{
+ volatile unsigned short* w_ptr;
+ unsigned len_w = len >> 1; /* region len in words */
+ unsigned i;
+
+ for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+ writew (0xAA55, w_ptr);
+
+ for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+ if (readw (w_ptr) != 0xAA55) {
+ len_w = i;
+ break;
+ }
+
+ for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+ writew (0x55AA, w_ptr);
+
+ for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+ if (readw(w_ptr) != 0x55AA) {
+ len_w = i;
+ break;
+ }
+
+ for (i = 0, w_ptr = ptr; i < len_w; ++i, ++w_ptr)
+ writew (0, w_ptr);
+
+ return len_w << 1;
+}
+
+/*============================================================================
+ * Calculate 16-bit CRC using CCITT polynomial.
+ */
+static unsigned short checksum (unsigned char* buf, unsigned len)
+{
+ unsigned short crc = 0;
+ unsigned mask, flag;
+
+ for (; len; --len, ++buf) {
+ for (mask = 0x80; mask; mask >>= 1) {
+ flag = (crc & 0x8000);
+ crc <<= 1;
+ crc |= ((*buf & mask) ? 1 : 0);
+ if (flag) crc ^= 0x1021;
+ }
+ }
+ return crc;
+}
+
+static int init_pci_slot(sdlahw_t *hw)
+{
+
+ u32 int_status;
+ int volatile found=0;
+ int i=0;
+
+ /* Check if this is a very first load for a specific
+ * pci card. If it is, clear the interrput bits, and
+ * set the flag indicating that this card was initialized.
+ */
+
+ for (i=0; (i<MAX_S514_CARDS) && !found; i++){
+ if (pci_slot_ar[i] == hw->S514_slot_no){
+ found=1;
+ break;
+ }
+ if (pci_slot_ar[i] == 0xFF){
+ break;
+ }
+ }
+
+ if (!found){
+ read_S514_int_stat(hw,&int_status);
+ S514_intack(hw,int_status);
+ if (i == MAX_S514_CARDS){
+ printk(KERN_INFO "%s: Critical Error !!!\n",modname);
+ printk(KERN_INFO
+ "%s: Number of Sangoma PCI cards exceeded maximum limit.\n",
+ modname);
+ printk(KERN_INFO "Please contact Sangoma Technologies\n");
+ return 1;
+ }
+ pci_slot_ar[i] = hw->S514_slot_no;
+ }
+ return 0;
+}
+
+static int pci_probe(sdlahw_t *hw)
+{
+
+ unsigned char slot_no;
+ int number_S514_cards = 0;
+ u16 PCI_subsys_vendor;
+ u16 PCI_card_type;
+
+ struct pci_dev *pci_dev = NULL;
+ struct pci_bus *bus = NULL;
+
+ slot_no = 0;
+
+ while ((pci_dev = pci_find_device(V3_VENDOR_ID, V3_DEVICE_ID, pci_dev))
+ != NULL) {
+
+ pci_read_config_word(pci_dev, PCI_SUBSYS_VENDOR_WORD,
+ &PCI_subsys_vendor);
+
+ if(PCI_subsys_vendor != SANGOMA_SUBSYS_VENDOR)
+ continue;
+
+ pci_read_config_word(pci_dev, PCI_CARD_TYPE,
+ &PCI_card_type);
+
+ bus = pci_dev->bus;
+
+ /* A dual cpu card can support up to 4 physical connections,
+ * where a single cpu card can support up to 2 physical
+ * connections. The FT1 card can only support a single
+ * connection, however we cannot distinguish between a Single
+ * CPU card and an FT1 card. */
+ if (PCI_card_type == S514_DUAL_CPU){
+ number_S514_cards += 4;
+ printk(KERN_INFO
+ "wanpipe: S514-PCI card found, cpu(s) 2, bus #%d, slot #%d, irq #%d\n",
+ bus->number,((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK),
+ pci_dev->irq);
+ }else{
+ number_S514_cards += 2;
+ printk(KERN_INFO
+ "wanpipe: S514-PCI card found, cpu(s) 1, bus #%d, slot #%d, irq #%d\n",
+ bus->number,((pci_dev->devfn >> 3) & PCI_DEV_SLOT_MASK),
+ pci_dev->irq);
+ }
+ }
+
+ return number_S514_cards;
+
+}
+
+
+
+EXPORT_SYMBOL(wanpipe_hw_probe);
+
+unsigned wanpipe_hw_probe(void)
+{
+ sdlahw_t hw;
+ unsigned* opt = s508_port_options;
+ unsigned cardno=0;
+ int i;
+
+ memset(&hw, 0, sizeof(hw));
+
+ for (i = 1; i <= opt[0]; i++) {
+ if (detect_s508(opt[i])){
+ /* S508 card can support up to two physical links */
+ cardno+=2;
+ printk(KERN_INFO "wanpipe: S508-ISA card found, port 0x%x\n",opt[i]);
+ }
+ }
+
+ #ifdef CONFIG_PCI
+ hw.S514_slot_no = 0;
+ cardno += pci_probe(&hw);
+ #else
+ printk(KERN_INFO "wanpipe: Warning, Kernel not compiled for PCI support!\n");
+ printk(KERN_INFO "wanpipe: PCI Hardware Probe Failed!\n");
+ #endif
+
+ return cardno;
+}
+
+/****** End *****************************************************************/
diff --git a/drivers/net/wan/sdlamain.c b/drivers/net/wan/sdlamain.c
new file mode 100644
index 00000000000..74e151acef3
--- /dev/null
+++ b/drivers/net/wan/sdlamain.c
@@ -0,0 +1,1341 @@
+/****************************************************************************
+* sdlamain.c WANPIPE(tm) Multiprotocol WAN Link Driver. Main module.
+*
+* Author: Nenad Corbic <ncorbic@sangoma.com>
+* Gideon Hack
+*
+* Copyright: (c) 1995-2000 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Dec 22, 2000 Nenad Corbic Updated for 2.4.X kernels.
+* Removed the polling routine.
+* Nov 13, 2000 Nenad Corbic Added hw probing on module load and dynamic
+* device allocation.
+* Nov 7, 2000 Nenad Corbic Fixed the Multi-Port PPP for kernels
+* 2.2.16 and above.
+* Aug 2, 2000 Nenad Corbic Block the Multi-Port PPP from running on
+* kernels 2.2.16 or greater. The SyncPPP
+* has changed.
+* Jul 25, 2000 Nenad Corbic Updated the Piggiback support for MultPPPP.
+* Jul 13, 2000 Nenad Corbic Added Multi-PPP support.
+* Feb 02, 2000 Nenad Corbic Fixed up piggyback probing and selection.
+* Sep 23, 1999 Nenad Corbic Added support for SMP
+* Sep 13, 1999 Nenad Corbic Each port is treated as a separate device.
+* Jun 02, 1999 Gideon Hack Added support for the S514 adapter.
+* Updates for Linux 2.2.X kernels.
+* Sep 17, 1998 Jaspreet Singh Updated for 2.1.121+ kernel
+* Nov 28, 1997 Jaspreet Singh Changed DRV_RELEASE to 1
+* Nov 10, 1997 Jaspreet Singh Changed sti() to restore_flags();
+* Nov 06, 1997 Jaspreet Singh Changed DRV_VERSION to 4 and DRV_RELEASE to 0
+* Oct 20, 1997 Jaspreet Singh Modified sdla_isr routine so that card->in_isr
+* assignments are taken out and placed in the
+* sdla_ppp.c, sdla_fr.c and sdla_x25.c isr
+* routines. Took out 'wandev->tx_int_enabled' and
+* replaced it with 'wandev->enable_tx_int'.
+* May 29, 1997 Jaspreet Singh Flow Control Problem
+* added "wandev->tx_int_enabled=1" line in the
+* init module. This line initializes the flag for
+* preventing Interrupt disabled with device set to
+* busy
+* Jan 15, 1997 Gene Kozin Version 3.1.0
+* o added UDP management stuff
+* Jan 02, 1997 Gene Kozin Initial version.
+*****************************************************************************/
+
+#include <linux/config.h> /* OS configuration options */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/init.h>
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/module.h> /* support for loadable modules */
+#include <linux/ioport.h> /* request_region(), release_region() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+
+#include <linux/in.h>
+#include <asm/io.h> /* phys_to_virt() */
+#include <linux/pci.h>
+#include <linux/sdlapci.h>
+#include <linux/if_wanpipe_common.h>
+
+#include <asm/uaccess.h> /* kernel <-> user copy */
+#include <linux/inetdevice.h>
+
+#include <linux/ip.h>
+#include <net/route.h>
+
+#define KMEM_SAFETYZONE 8
+
+
+#ifndef CONFIG_WANPIPE_FR
+ #define wpf_init(a,b) (-EPROTONOSUPPORT)
+#endif
+
+#ifndef CONFIG_WANPIPE_CHDLC
+ #define wpc_init(a,b) (-EPROTONOSUPPORT)
+#endif
+
+#ifndef CONFIG_WANPIPE_X25
+ #define wpx_init(a,b) (-EPROTONOSUPPORT)
+#endif
+
+#ifndef CONFIG_WANPIPE_PPP
+ #define wpp_init(a,b) (-EPROTONOSUPPORT)
+#endif
+
+#ifndef CONFIG_WANPIPE_MULTPPP
+ #define wsppp_init(a,b) (-EPROTONOSUPPORT)
+#endif
+
+
+/***********FOR DEBUGGING PURPOSES*********************************************
+static void * dbg_kmalloc(unsigned int size, int prio, int line) {
+ int i = 0;
+ void * v = kmalloc(size+sizeof(unsigned int)+2*KMEM_SAFETYZONE*8,prio);
+ char * c1 = v;
+ c1 += sizeof(unsigned int);
+ *((unsigned int *)v) = size;
+
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ c1[0] = 'D'; c1[1] = 'E'; c1[2] = 'A'; c1[3] = 'D';
+ c1[4] = 'B'; c1[5] = 'E'; c1[6] = 'E'; c1[7] = 'F';
+ c1 += 8;
+ }
+ c1 += size;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ c1[0] = 'M'; c1[1] = 'U'; c1[2] = 'N'; c1[3] = 'G';
+ c1[4] = 'W'; c1[5] = 'A'; c1[6] = 'L'; c1[7] = 'L';
+ c1 += 8;
+ }
+ v = ((char *)v) + sizeof(unsigned int) + KMEM_SAFETYZONE*8;
+ printk(KERN_INFO "line %d kmalloc(%d,%d) = %p\n",line,size,prio,v);
+ return v;
+}
+static void dbg_kfree(void * v, int line) {
+ unsigned int * sp = (unsigned int *)(((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8));
+ unsigned int size = *sp;
+ char * c1 = ((char *)v) - KMEM_SAFETYZONE*8;
+ int i = 0;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ if ( c1[0] != 'D' || c1[1] != 'E' || c1[2] != 'A' || c1[3] != 'D'
+ || c1[4] != 'B' || c1[5] != 'E' || c1[6] != 'E' || c1[7] != 'F') {
+ printk(KERN_INFO "kmalloced block at %p has been corrupted (underrun)!\n",v);
+ printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+ c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+ }
+ c1 += 8;
+ }
+ c1 += size;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ if ( c1[0] != 'M' || c1[1] != 'U' || c1[2] != 'N' || c1[3] != 'G'
+ || c1[4] != 'W' || c1[5] != 'A' || c1[6] != 'L' || c1[7] != 'L'
+ ) {
+ printk(KERN_INFO "kmalloced block at %p has been corrupted (overrun):\n",v);
+ printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+ c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+ }
+ c1 += 8;
+ }
+ printk(KERN_INFO "line %d kfree(%p)\n",line,v);
+ v = ((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8);
+ kfree(v);
+}
+
+#define kmalloc(x,y) dbg_kmalloc(x,y,__LINE__)
+#define kfree(x) dbg_kfree(x,__LINE__)
+******************************************************************************/
+
+
+
+/****** Defines & Macros ****************************************************/
+
+#ifdef _DEBUG_
+#define STATIC
+#else
+#define STATIC static
+#endif
+
+#define DRV_VERSION 5 /* version number */
+#define DRV_RELEASE 0 /* release (minor version) number */
+#define MAX_CARDS 16 /* max number of adapters */
+
+#ifndef CONFIG_WANPIPE_CARDS /* configurable option */
+#define CONFIG_WANPIPE_CARDS 1
+#endif
+
+#define CMD_OK 0 /* normal firmware return code */
+#define CMD_TIMEOUT 0xFF /* firmware command timed out */
+#define MAX_CMD_RETRY 10 /* max number of firmware retries */
+/****** Function Prototypes *************************************************/
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/* WAN link driver entry points */
+static int setup(struct wan_device* wandev, wandev_conf_t* conf);
+static int shutdown(struct wan_device* wandev);
+static int ioctl(struct wan_device* wandev, unsigned cmd, unsigned long arg);
+
+/* IOCTL handlers */
+static int ioctl_dump (sdla_t* card, sdla_dump_t* u_dump);
+static int ioctl_exec (sdla_t* card, sdla_exec_t* u_exec, int);
+
+/* Miscellaneous functions */
+STATIC irqreturn_t sdla_isr (int irq, void* dev_id, struct pt_regs *regs);
+static void release_hw (sdla_t *card);
+
+static int check_s508_conflicts (sdla_t* card,wandev_conf_t* conf, int*);
+static int check_s514_conflicts (sdla_t* card,wandev_conf_t* conf, int*);
+
+
+/****** Global Data **********************************************************
+ * Note: All data must be explicitly initialized!!!
+ */
+
+/* private data */
+static char drvname[] = "wanpipe";
+static char fullname[] = "WANPIPE(tm) Multiprotocol Driver";
+static char copyright[] = "(c) 1995-2000 Sangoma Technologies Inc.";
+static int ncards;
+static sdla_t* card_array; /* adapter data space */
+
+/* Wanpipe's own workqueue, used for all API's.
+ * All protocol specific tasks will be inserted
+ * into the "wanpipe_wq" workqueue.
+
+ * The kernel workqueue mechanism will execute
+ * all pending tasks in the "wanpipe_wq" workqueue.
+ */
+
+struct workqueue_struct *wanpipe_wq;
+DECLARE_WORK(wanpipe_work, NULL, NULL);
+
+static int wanpipe_bh_critical;
+
+/******* Kernel Loadable Module Entry Points ********************************/
+
+/*============================================================================
+ * Module 'insert' entry point.
+ * o print announcement
+ * o allocate adapter data space
+ * o initialize static data
+ * o register all cards with WAN router
+ * o calibrate SDLA shared memory access delay.
+ *
+ * Return: 0 Ok
+ * < 0 error.
+ * Context: process
+ */
+
+static int __init wanpipe_init(void)
+{
+ int cnt, err = 0;
+
+ printk(KERN_INFO "%s v%u.%u %s\n",
+ fullname, DRV_VERSION, DRV_RELEASE, copyright);
+
+ wanpipe_wq = create_workqueue("wanpipe_wq");
+ if (!wanpipe_wq)
+ return -ENOMEM;
+
+ /* Probe for wanpipe cards and return the number found */
+ printk(KERN_INFO "wanpipe: Probing for WANPIPE hardware.\n");
+ ncards = wanpipe_hw_probe();
+ if (ncards){
+ printk(KERN_INFO "wanpipe: Allocating maximum %i devices: wanpipe%i - wanpipe%i.\n",ncards,1,ncards);
+ }else{
+ printk(KERN_INFO "wanpipe: No S514/S508 cards found, unloading modules!\n");
+ destroy_workqueue(wanpipe_wq);
+ return -ENODEV;
+ }
+
+ /* Verify number of cards and allocate adapter data space */
+ card_array = kmalloc(sizeof(sdla_t) * ncards, GFP_KERNEL);
+ if (card_array == NULL) {
+ destroy_workqueue(wanpipe_wq);
+ return -ENOMEM;
+ }
+
+ memset(card_array, 0, sizeof(sdla_t) * ncards);
+
+ /* Register adapters with WAN router */
+ for (cnt = 0; cnt < ncards; ++ cnt) {
+ sdla_t* card = &card_array[cnt];
+ struct wan_device* wandev = &card->wandev;
+
+ card->next = NULL;
+ sprintf(card->devname, "%s%d", drvname, cnt + 1);
+ wandev->magic = ROUTER_MAGIC;
+ wandev->name = card->devname;
+ wandev->private = card;
+ wandev->enable_tx_int = 0;
+ wandev->setup = &setup;
+ wandev->shutdown = &shutdown;
+ wandev->ioctl = &ioctl;
+ err = register_wan_device(wandev);
+ if (err) {
+ printk(KERN_INFO
+ "%s: %s registration failed with error %d!\n",
+ drvname, card->devname, err);
+ break;
+ }
+ }
+ if (cnt){
+ ncards = cnt; /* adjust actual number of cards */
+ }else {
+ kfree(card_array);
+ destroy_workqueue(wanpipe_wq);
+ printk(KERN_INFO "IN Init Module: NO Cards registered\n");
+ err = -ENODEV;
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Module 'remove' entry point.
+ * o unregister all adapters from the WAN router
+ * o release all remaining system resources
+ */
+static void __exit wanpipe_cleanup(void)
+{
+ int i;
+
+ if (!ncards)
+ return;
+
+ for (i = 0; i < ncards; ++i) {
+ sdla_t* card = &card_array[i];
+ unregister_wan_device(card->devname);
+ }
+ destroy_workqueue(wanpipe_wq);
+ kfree(card_array);
+
+ printk(KERN_INFO "\nwanpipe: WANPIPE Modules Unloaded.\n");
+}
+
+module_init(wanpipe_init);
+module_exit(wanpipe_cleanup);
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Setup/configure WAN link driver.
+ * o check adapter state
+ * o make sure firmware is present in configuration
+ * o make sure I/O port and IRQ are specified
+ * o make sure I/O region is available
+ * o allocate interrupt vector
+ * o setup SDLA hardware
+ * o call appropriate routine to perform protocol-specific initialization
+ * o mark I/O region as used
+ * o if this is the first active card, then schedule background task
+ *
+ * This function is called when router handles ROUTER_SETUP IOCTL. The
+ * configuration structure is in kernel memory (including extended data, if
+ * any).
+ */
+
+static int setup(struct wan_device* wandev, wandev_conf_t* conf)
+{
+ sdla_t* card;
+ int err = 0;
+ int irq=0;
+
+ /* Sanity checks */
+ if ((wandev == NULL) || (wandev->private == NULL) || (conf == NULL)){
+ printk(KERN_INFO
+ "%s: Failed Sdlamain Setup wandev %u, card %u, conf %u !\n",
+ wandev->name,
+ (unsigned int)wandev,(unsigned int)wandev->private,
+ (unsigned int)conf);
+ return -EFAULT;
+ }
+
+ printk(KERN_INFO "%s: Starting WAN Setup\n", wandev->name);
+
+ card = wandev->private;
+ if (wandev->state != WAN_UNCONFIGURED){
+ printk(KERN_INFO "%s: failed sdlamain setup, busy!\n",
+ wandev->name);
+ return -EBUSY; /* already configured */
+ }
+
+ printk(KERN_INFO "\nProcessing WAN device %s...\n", wandev->name);
+
+ /* Initialize the counters for each wandev
+ * Used for counting number of times new_if and
+ * del_if get called.
+ */
+ wandev->del_if_cnt = 0;
+ wandev->new_if_cnt = 0;
+ wandev->config_id = conf->config_id;
+
+ if (!conf->data_size || (conf->data == NULL)) {
+ printk(KERN_INFO
+ "%s: firmware not found in configuration data!\n",
+ wandev->name);
+ return -EINVAL;
+ }
+
+ /* Check for resource conflicts and setup the
+ * card for piggibacking if necessary */
+ if(!conf->S514_CPU_no[0]) {
+ if ((err=check_s508_conflicts(card,conf,&irq)) != 0){
+ return err;
+ }
+ }else {
+ if ((err=check_s514_conflicts(card,conf,&irq)) != 0){
+ return err;
+ }
+ }
+
+ /* If the current card has already been configured
+ * or it's a piggyback card, do not try to allocate
+ * resources.
+ */
+ if (!card->wandev.piggyback && !card->configured){
+
+ /* Configure hardware, load firmware, etc. */
+ memset(&card->hw, 0, sizeof(sdlahw_t));
+
+ /* for an S514 adapter, pass the CPU number and the slot number read */
+ /* from 'router.conf' to the 'sdla_setup()' function via the 'port' */
+ /* parameter */
+ if (conf->S514_CPU_no[0]){
+
+ card->hw.S514_cpu_no[0] = conf->S514_CPU_no[0];
+ card->hw.S514_slot_no = conf->PCI_slot_no;
+ card->hw.auto_pci_cfg = conf->auto_pci_cfg;
+
+ if (card->hw.auto_pci_cfg == WANOPT_YES){
+ printk(KERN_INFO "%s: Setting CPU to %c and Slot to Auto\n",
+ card->devname, card->hw.S514_cpu_no[0]);
+ }else{
+ printk(KERN_INFO "%s: Setting CPU to %c and Slot to %i\n",
+ card->devname, card->hw.S514_cpu_no[0], card->hw.S514_slot_no);
+ }
+
+ }else{
+ /* 508 Card io port and irq initialization */
+ card->hw.port = conf->ioport;
+ card->hw.irq = (conf->irq == 9) ? 2 : conf->irq;
+ }
+
+
+ /* Compute the virtual address of the card in kernel space */
+ if(conf->maddr){
+ card->hw.dpmbase = phys_to_virt(conf->maddr);
+ }else{
+ card->hw.dpmbase = (void *)conf->maddr;
+ }
+
+ card->hw.dpmsize = SDLA_WINDOWSIZE;
+
+ /* set the adapter type if using an S514 adapter */
+ card->hw.type = (conf->S514_CPU_no[0]) ? SDLA_S514 : conf->hw_opt[0];
+ card->hw.pclk = conf->hw_opt[1];
+
+ err = sdla_setup(&card->hw, conf->data, conf->data_size);
+ if (err){
+ printk(KERN_INFO "%s: Hardware setup Failed %i\n",
+ card->devname,err);
+ return err;
+ }
+
+ if(card->hw.type != SDLA_S514)
+ irq = (conf->irq == 2) ? 9 : conf->irq; /* IRQ2 -> IRQ9 */
+ else
+ irq = card->hw.irq;
+
+ /* request an interrupt vector - note that interrupts may be shared */
+ /* when using the S514 PCI adapter */
+
+ if(request_irq(irq, sdla_isr,
+ (card->hw.type == SDLA_S514) ? SA_SHIRQ : 0,
+ wandev->name, card)){
+
+ printk(KERN_INFO "%s: Can't reserve IRQ %d!\n", wandev->name, irq);
+ return -EINVAL;
+ }
+
+ }else{
+ printk(KERN_INFO "%s: Card Configured %lu or Piggybacking %i!\n",
+ wandev->name,card->configured,card->wandev.piggyback);
+ }
+
+
+ if (!card->configured){
+
+ /* Initialize the Spin lock */
+ printk(KERN_INFO "%s: Initializing for SMP\n",wandev->name);
+
+ /* Piggyback spin lock has already been initialized,
+ * in check_s514/s508_conflicts() */
+ if (!card->wandev.piggyback){
+ spin_lock_init(&card->wandev.lock);
+ }
+
+ /* Intialize WAN device data space */
+ wandev->irq = irq;
+ wandev->dma = 0;
+ if(card->hw.type != SDLA_S514){
+ wandev->ioport = card->hw.port;
+ }else{
+ wandev->S514_cpu_no[0] = card->hw.S514_cpu_no[0];
+ wandev->S514_slot_no = card->hw.S514_slot_no;
+ }
+ wandev->maddr = (unsigned long)card->hw.dpmbase;
+ wandev->msize = card->hw.dpmsize;
+ wandev->hw_opt[0] = card->hw.type;
+ wandev->hw_opt[1] = card->hw.pclk;
+ wandev->hw_opt[2] = card->hw.memory;
+ wandev->hw_opt[3] = card->hw.fwid;
+ }
+
+ /* Protocol-specific initialization */
+ switch (card->hw.fwid) {
+
+ case SFID_X25_502:
+ case SFID_X25_508:
+ printk(KERN_INFO "%s: Starting X.25 Protocol Init.\n",
+ card->devname);
+ err = wpx_init(card, conf);
+ break;
+ case SFID_FR502:
+ case SFID_FR508:
+ printk(KERN_INFO "%s: Starting Frame Relay Protocol Init.\n",
+ card->devname);
+ err = wpf_init(card, conf);
+ break;
+ case SFID_PPP502:
+ case SFID_PPP508:
+ printk(KERN_INFO "%s: Starting PPP Protocol Init.\n",
+ card->devname);
+ err = wpp_init(card, conf);
+ break;
+
+ case SFID_CHDLC508:
+ case SFID_CHDLC514:
+ if (conf->ft1){
+ printk(KERN_INFO "%s: Starting FT1 CSU/DSU Config Driver.\n",
+ card->devname);
+ err = wpft1_init(card, conf);
+ break;
+
+ }else if (conf->config_id == WANCONFIG_MPPP){
+ printk(KERN_INFO "%s: Starting Multi-Port PPP Protocol Init.\n",
+ card->devname);
+ err = wsppp_init(card,conf);
+ break;
+
+ }else{
+ printk(KERN_INFO "%s: Starting CHDLC Protocol Init.\n",
+ card->devname);
+ err = wpc_init(card, conf);
+ break;
+ }
+ default:
+ printk(KERN_INFO "%s: Error, Firmware is not supported %X %X!\n",
+ wandev->name,card->hw.fwid,SFID_CHDLC508);
+ err = -EPROTONOSUPPORT;
+ }
+
+ if (err != 0){
+ if (err == -EPROTONOSUPPORT){
+ printk(KERN_INFO
+ "%s: Error, Protocol selected has not been compiled!\n",
+ card->devname);
+ printk(KERN_INFO
+ "%s: Re-configure the kernel and re-build the modules!\n",
+ card->devname);
+ }
+
+ release_hw(card);
+ wandev->state = WAN_UNCONFIGURED;
+ return err;
+ }
+
+
+ /* Reserve I/O region and schedule background task */
+ if(card->hw.type != SDLA_S514 && !card->wandev.piggyback)
+ if (!request_region(card->hw.port, card->hw.io_range,
+ wandev->name)) {
+ printk(KERN_WARNING "port 0x%04x busy\n", card->hw.port);
+ release_hw(card);
+ wandev->state = WAN_UNCONFIGURED;
+ return -EBUSY;
+ }
+
+ /* Only use the polling routine for the X25 protocol */
+
+ card->wandev.critical=0;
+ return 0;
+}
+
+/*==================================================================
+ * configure_s508_card
+ *
+ * For a S508 adapter, check for a possible configuration error in that
+ * we are loading an adapter in the same IO port as a previously loaded S508
+ * card.
+ */
+
+static int check_s508_conflicts (sdla_t* card,wandev_conf_t* conf, int *irq)
+{
+ unsigned long smp_flags;
+ int i;
+
+ if (conf->ioport <= 0) {
+ printk(KERN_INFO
+ "%s: can't configure without I/O port address!\n",
+ card->wandev.name);
+ return -EINVAL;
+ }
+
+ if (conf->irq <= 0) {
+ printk(KERN_INFO "%s: can't configure without IRQ!\n",
+ card->wandev.name);
+ return -EINVAL;
+ }
+
+ if (test_bit(0,&card->configured))
+ return 0;
+
+
+ /* Check for already loaded card with the same IO port and IRQ
+ * If found, copy its hardware configuration and use its
+ * resources (i.e. piggybacking)
+ */
+
+ for (i = 0; i < ncards; i++) {
+ sdla_t *nxt_card = &card_array[i];
+
+ /* Skip the current card ptr */
+ if (nxt_card == card)
+ continue;
+
+
+ /* Find a card that is already configured with the
+ * same IO Port */
+ if ((nxt_card->hw.type == SDLA_S508) &&
+ (nxt_card->hw.port == conf->ioport) &&
+ (nxt_card->next == NULL)){
+
+ /* We found a card the card that has same configuration
+ * as us. This means, that we must setup this card in
+ * piggibacking mode. However, only CHDLC and MPPP protocol
+ * support this setup */
+
+ if ((conf->config_id == WANCONFIG_CHDLC ||
+ conf->config_id == WANCONFIG_MPPP) &&
+ (nxt_card->wandev.config_id == WANCONFIG_CHDLC ||
+ nxt_card->wandev.config_id == WANCONFIG_MPPP)){
+
+ *irq = nxt_card->hw.irq;
+ memcpy(&card->hw, &nxt_card->hw, sizeof(sdlahw_t));
+
+ /* The master could already be running, we must
+ * set this as a critical area */
+ lock_adapter_irq(&nxt_card->wandev.lock, &smp_flags);
+
+ nxt_card->next = card;
+ card->next = nxt_card;
+
+ card->wandev.piggyback = WANOPT_YES;
+
+ /* We must initialise the piggiback spin lock here
+ * since isr will try to lock card->next if it
+ * exists */
+ spin_lock_init(&card->wandev.lock);
+
+ unlock_adapter_irq(&nxt_card->wandev.lock, &smp_flags);
+ break;
+ }else{
+ /* Trying to run piggibacking with a wrong protocol */
+ printk(KERN_INFO "%s: ERROR: Resource busy, ioport: 0x%x\n"
+ "%s: This protocol doesn't support\n"
+ "%s: multi-port operation!\n",
+ card->devname,nxt_card->hw.port,
+ card->devname,card->devname);
+ return -EEXIST;
+ }
+ }
+ }
+
+
+ /* Make sure I/O port region is available only if we are the
+ * master device. If we are running in piggybacking mode,
+ * we will use the resources of the master card. */
+ if (!card->wandev.piggyback) {
+ struct resource *rr =
+ request_region(conf->ioport, SDLA_MAXIORANGE, "sdlamain");
+ release_region(conf->ioport, SDLA_MAXIORANGE);
+
+ if (!rr) {
+ printk(KERN_INFO
+ "%s: I/O region 0x%X - 0x%X is in use!\n",
+ card->wandev.name, conf->ioport,
+ conf->ioport + SDLA_MAXIORANGE - 1);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*==================================================================
+ * configure_s514_card
+ *
+ * For a S514 adapter, check for a possible configuration error in that
+ * we are loading an adapter in the same slot as a previously loaded S514
+ * card.
+ */
+
+
+static int check_s514_conflicts(sdla_t* card,wandev_conf_t* conf, int *irq)
+{
+ unsigned long smp_flags;
+ int i;
+
+ if (test_bit(0,&card->configured))
+ return 0;
+
+
+ /* Check for already loaded card with the same IO port and IRQ
+ * If found, copy its hardware configuration and use its
+ * resources (i.e. piggybacking)
+ */
+
+ for (i = 0; i < ncards; i ++) {
+
+ sdla_t* nxt_card = &card_array[i];
+ if(nxt_card == card)
+ continue;
+
+ if((nxt_card->hw.type == SDLA_S514) &&
+ (nxt_card->hw.S514_slot_no == conf->PCI_slot_no) &&
+ (nxt_card->hw.S514_cpu_no[0] == conf->S514_CPU_no[0])&&
+ (nxt_card->next == NULL)){
+
+
+ if ((conf->config_id == WANCONFIG_CHDLC ||
+ conf->config_id == WANCONFIG_MPPP) &&
+ (nxt_card->wandev.config_id == WANCONFIG_CHDLC ||
+ nxt_card->wandev.config_id == WANCONFIG_MPPP)){
+
+ *irq = nxt_card->hw.irq;
+ memcpy(&card->hw, &nxt_card->hw, sizeof(sdlahw_t));
+
+ /* The master could already be running, we must
+ * set this as a critical area */
+ lock_adapter_irq(&nxt_card->wandev.lock,&smp_flags);
+ nxt_card->next = card;
+ card->next = nxt_card;
+
+ card->wandev.piggyback = WANOPT_YES;
+
+ /* We must initialise the piggiback spin lock here
+ * since isr will try to lock card->next if it
+ * exists */
+ spin_lock_init(&card->wandev.lock);
+
+ unlock_adapter_irq(&nxt_card->wandev.lock,&smp_flags);
+
+ }else{
+ /* Trying to run piggibacking with a wrong protocol */
+ printk(KERN_INFO "%s: ERROR: Resource busy: CPU %c PCISLOT %i\n"
+ "%s: This protocol doesn't support\n"
+ "%s: multi-port operation!\n",
+ card->devname,
+ conf->S514_CPU_no[0],conf->PCI_slot_no,
+ card->devname,card->devname);
+ return -EEXIST;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+
+/*============================================================================
+ * Shut down WAN link driver.
+ * o shut down adapter hardware
+ * o release system resources.
+ *
+ * This function is called by the router when device is being unregistered or
+ * when it handles ROUTER_DOWN IOCTL.
+ */
+static int shutdown(struct wan_device* wandev)
+{
+ sdla_t *card;
+ int err=0;
+
+ /* sanity checks */
+ if ((wandev == NULL) || (wandev->private == NULL)){
+ return -EFAULT;
+ }
+
+ if (wandev->state == WAN_UNCONFIGURED){
+ return 0;
+ }
+
+ card = wandev->private;
+
+ if (card->tty_opt){
+ if (card->tty_open){
+ printk(KERN_INFO
+ "%s: Shutdown Failed: TTY is still open\n",
+ card->devname);
+ return -EBUSY;
+ }
+ }
+
+ wandev->state = WAN_UNCONFIGURED;
+
+ set_bit(PERI_CRIT,(void*)&wandev->critical);
+
+ /* In case of piggibacking, make sure that
+ * we never try to shutdown both devices at the same
+ * time, because they depend on one another */
+
+ if (card->disable_comm){
+ card->disable_comm(card);
+ }
+
+ /* Release Resources */
+ release_hw(card);
+
+ /* only free the allocated I/O range if not an S514 adapter */
+ if (wandev->hw_opt[0] != SDLA_S514 && !card->configured){
+ release_region(card->hw.port, card->hw.io_range);
+ }
+
+ if (!card->configured){
+ memset(&card->hw, 0, sizeof(sdlahw_t));
+ if (card->next){
+ memset(&card->next->hw, 0, sizeof(sdlahw_t));
+ }
+ }
+
+
+ clear_bit(PERI_CRIT,(void*)&wandev->critical);
+ return err;
+}
+
+static void release_hw (sdla_t *card)
+{
+ sdla_t *nxt_card;
+
+
+ /* Check if next device exists */
+ if (card->next){
+ nxt_card = card->next;
+ /* If next device is down then release resources */
+ if (nxt_card->wandev.state == WAN_UNCONFIGURED){
+ if (card->wandev.piggyback){
+ /* If this device is piggyback then use
+ * information of the master device
+ */
+ printk(KERN_INFO "%s: Piggyback shutting down\n",card->devname);
+ sdla_down(&card->next->hw);
+ free_irq(card->wandev.irq, card->next);
+ card->configured = 0;
+ card->next->configured = 0;
+ card->wandev.piggyback = 0;
+ }else{
+ /* Master device shutting down */
+ printk(KERN_INFO "%s: Master shutting down\n",card->devname);
+ sdla_down(&card->hw);
+ free_irq(card->wandev.irq, card);
+ card->configured = 0;
+ card->next->configured = 0;
+ }
+ }else{
+ printk(KERN_INFO "%s: Device still running %i\n",
+ nxt_card->devname,nxt_card->wandev.state);
+
+ card->configured = 1;
+ }
+ }else{
+ printk(KERN_INFO "%s: Master shutting down\n",card->devname);
+ sdla_down(&card->hw);
+ free_irq(card->wandev.irq, card);
+ card->configured = 0;
+ }
+ return;
+}
+
+
+/*============================================================================
+ * Driver I/O control.
+ * o verify arguments
+ * o perform requested action
+ *
+ * This function is called when router handles one of the reserved user
+ * IOCTLs. Note that 'arg' stil points to user address space.
+ */
+static int ioctl(struct wan_device* wandev, unsigned cmd, unsigned long arg)
+{
+ sdla_t* card;
+ int err;
+
+ /* sanity checks */
+ if ((wandev == NULL) || (wandev->private == NULL))
+ return -EFAULT;
+ if (wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ card = wandev->private;
+
+ if(card->hw.type != SDLA_S514){
+ disable_irq(card->hw.irq);
+ }
+
+ if (test_bit(SEND_CRIT, (void*)&wandev->critical)) {
+ return -EAGAIN;
+ }
+
+ switch (cmd) {
+ case WANPIPE_DUMP:
+ err = ioctl_dump(wandev->private, (void*)arg);
+ break;
+
+ case WANPIPE_EXEC:
+ err = ioctl_exec(wandev->private, (void*)arg, cmd);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+/****** Driver IOCTL Handlers ***********************************************/
+
+/*============================================================================
+ * Dump adapter memory to user buffer.
+ * o verify request structure
+ * o copy request structure to kernel data space
+ * o verify length/offset
+ * o verify user buffer
+ * o copy adapter memory image to user buffer
+ *
+ * Note: when dumping memory, this routine switches curent dual-port memory
+ * vector, so care must be taken to avoid racing conditions.
+ */
+static int ioctl_dump (sdla_t* card, sdla_dump_t* u_dump)
+{
+ sdla_dump_t dump;
+ unsigned winsize;
+ unsigned long oldvec; /* DPM window vector */
+ unsigned long smp_flags;
+ int err = 0;
+
+ if(copy_from_user((void*)&dump, (void*)u_dump, sizeof(sdla_dump_t)))
+ return -EFAULT;
+
+ if ((dump.magic != WANPIPE_MAGIC) ||
+ (dump.offset + dump.length > card->hw.memory))
+ return -EINVAL;
+
+ winsize = card->hw.dpmsize;
+
+ if(card->hw.type != SDLA_S514) {
+
+ lock_adapter_irq(&card->wandev.lock, &smp_flags);
+
+ oldvec = card->hw.vector;
+ while (dump.length) {
+ /* current offset */
+ unsigned pos = dump.offset % winsize;
+ /* current vector */
+ unsigned long vec = dump.offset - pos;
+ unsigned len = (dump.length > (winsize - pos)) ?
+ (winsize - pos) : dump.length;
+ /* relocate window */
+ if (sdla_mapmem(&card->hw, vec) != 0) {
+ err = -EIO;
+ break;
+ }
+
+ if(copy_to_user((void *)dump.ptr,
+ (u8 *)card->hw.dpmbase + pos, len)){
+
+ unlock_adapter_irq(&card->wandev.lock, &smp_flags);
+ return -EFAULT;
+ }
+
+ dump.length -= len;
+ dump.offset += len;
+ dump.ptr = (char*)dump.ptr + len;
+ }
+
+ sdla_mapmem(&card->hw, oldvec);/* restore DPM window position */
+ unlock_adapter_irq(&card->wandev.lock, &smp_flags);
+
+ }else {
+
+ if(copy_to_user((void *)dump.ptr,
+ (u8 *)card->hw.dpmbase + dump.offset, dump.length)){
+ return -EFAULT;
+ }
+ }
+
+ return err;
+}
+
+/*============================================================================
+ * Execute adapter firmware command.
+ * o verify request structure
+ * o copy request structure to kernel data space
+ * o call protocol-specific 'exec' function
+ */
+static int ioctl_exec (sdla_t* card, sdla_exec_t* u_exec, int cmd)
+{
+ sdla_exec_t exec;
+ int err=0;
+
+ if (card->exec == NULL && cmd == WANPIPE_EXEC){
+ return -ENODEV;
+ }
+
+ if(copy_from_user((void*)&exec, (void*)u_exec, sizeof(sdla_exec_t)))
+ return -EFAULT;
+
+ if ((exec.magic != WANPIPE_MAGIC) || (exec.cmd == NULL))
+ return -EINVAL;
+
+ switch (cmd) {
+ case WANPIPE_EXEC:
+ err = card->exec(card, exec.cmd, exec.data);
+ break;
+ }
+ return err;
+}
+
+/******* Miscellaneous ******************************************************/
+
+/*============================================================================
+ * SDLA Interrupt Service Routine.
+ * o acknowledge SDLA hardware interrupt.
+ * o call protocol-specific interrupt service routine, if any.
+ */
+STATIC irqreturn_t sdla_isr (int irq, void* dev_id, struct pt_regs *regs)
+{
+#define card ((sdla_t*)dev_id)
+
+ if(card->hw.type == SDLA_S514) { /* handle interrrupt on S514 */
+ u32 int_status;
+ unsigned char CPU_no = card->hw.S514_cpu_no[0];
+ unsigned char card_found_for_IRQ;
+ u8 IRQ_count = 0;
+
+ for(;;) {
+
+ read_S514_int_stat(&card->hw, &int_status);
+
+ /* check if the interrupt is for this device */
+ if(!((unsigned char)int_status &
+ (IRQ_CPU_A | IRQ_CPU_B)))
+ return IRQ_HANDLED;
+
+ /* if the IRQ is for both CPUs on the same adapter, */
+ /* then alter the interrupt status so as to handle */
+ /* one CPU at a time */
+ if(((unsigned char)int_status & (IRQ_CPU_A | IRQ_CPU_B))
+ == (IRQ_CPU_A | IRQ_CPU_B)) {
+ int_status &= (CPU_no == S514_CPU_A) ?
+ ~IRQ_CPU_B : ~IRQ_CPU_A;
+ }
+
+ card_found_for_IRQ = 0;
+
+ /* check to see that the CPU number for this device */
+ /* corresponds to the interrupt status read */
+ switch (CPU_no) {
+ case S514_CPU_A:
+ if((unsigned char)int_status &
+ IRQ_CPU_A)
+ card_found_for_IRQ = 1;
+ break;
+
+ case S514_CPU_B:
+ if((unsigned char)int_status &
+ IRQ_CPU_B)
+ card_found_for_IRQ = 1;
+ break;
+ }
+
+ /* exit if the interrupt is for another CPU on the */
+ /* same IRQ */
+ if(!card_found_for_IRQ)
+ return IRQ_HANDLED;
+
+ if (!card ||
+ (card->wandev.state == WAN_UNCONFIGURED && !card->configured)){
+ printk(KERN_INFO
+ "Received IRQ %d for CPU #%c\n",
+ irq, CPU_no);
+ printk(KERN_INFO
+ "IRQ for unconfigured adapter\n");
+ S514_intack(&card->hw, int_status);
+ return IRQ_HANDLED;
+ }
+
+ if (card->in_isr) {
+ printk(KERN_INFO
+ "%s: interrupt re-entrancy on IRQ %d\n",
+ card->devname, card->wandev.irq);
+ S514_intack(&card->hw, int_status);
+ return IRQ_HANDLED;
+ }
+
+ spin_lock(&card->wandev.lock);
+ if (card->next){
+ spin_lock(&card->next->wandev.lock);
+ }
+
+ S514_intack(&card->hw, int_status);
+ if (card->isr)
+ card->isr(card);
+
+ if (card->next){
+ spin_unlock(&card->next->wandev.lock);
+ }
+ spin_unlock(&card->wandev.lock);
+
+ /* handle a maximum of two interrupts (one for each */
+ /* CPU on the adapter) before returning */
+ if((++ IRQ_count) == 2)
+ return IRQ_HANDLED;
+ }
+ }
+
+ else { /* handle interrupt on S508 adapter */
+
+ if (!card || ((card->wandev.state == WAN_UNCONFIGURED) && !card->configured))
+ return IRQ_HANDLED;
+
+ if (card->in_isr) {
+ printk(KERN_INFO
+ "%s: interrupt re-entrancy on IRQ %d!\n",
+ card->devname, card->wandev.irq);
+ return IRQ_HANDLED;
+ }
+
+ spin_lock(&card->wandev.lock);
+ if (card->next){
+ spin_lock(&card->next->wandev.lock);
+ }
+
+ sdla_intack(&card->hw);
+ if (card->isr)
+ card->isr(card);
+
+ if (card->next){
+ spin_unlock(&card->next->wandev.lock);
+ }
+ spin_unlock(&card->wandev.lock);
+
+ }
+ return IRQ_HANDLED;
+#undef card
+}
+
+/*============================================================================
+ * This routine is called by the protocol-specific modules when network
+ * interface is being open. The only reason we need this, is because we
+ * have to call MOD_INC_USE_COUNT, but cannot include 'module.h' where it's
+ * defined more than once into the same kernel module.
+ */
+void wanpipe_open (sdla_t* card)
+{
+ ++card->open_cnt;
+}
+
+/*============================================================================
+ * This routine is called by the protocol-specific modules when network
+ * interface is being closed. The only reason we need this, is because we
+ * have to call MOD_DEC_USE_COUNT, but cannot include 'module.h' where it's
+ * defined more than once into the same kernel module.
+ */
+void wanpipe_close (sdla_t* card)
+{
+ --card->open_cnt;
+}
+
+/*============================================================================
+ * Set WAN device state.
+ */
+void wanpipe_set_state (sdla_t* card, int state)
+{
+ if (card->wandev.state != state) {
+ switch (state) {
+ case WAN_CONNECTED:
+ printk (KERN_INFO "%s: link connected!\n",
+ card->devname);
+ break;
+
+ case WAN_CONNECTING:
+ printk (KERN_INFO "%s: link connecting...\n",
+ card->devname);
+ break;
+
+ case WAN_DISCONNECTED:
+ printk (KERN_INFO "%s: link disconnected!\n",
+ card->devname);
+ break;
+ }
+ card->wandev.state = state;
+ }
+ card->state_tick = jiffies;
+}
+
+sdla_t * wanpipe_find_card (char *name)
+{
+ int cnt;
+ for (cnt = 0; cnt < ncards; ++ cnt) {
+ sdla_t* card = &card_array[cnt];
+ if (!strcmp(card->devname,name))
+ return card;
+ }
+ return NULL;
+}
+
+sdla_t * wanpipe_find_card_num (int num)
+{
+ if (num < 1 || num > ncards)
+ return NULL;
+ num--;
+ return &card_array[num];
+}
+
+/*
+ * @work_pointer: work_struct to be done;
+ * should already have PREPARE_WORK() or
+ * INIT_WORK() done on it by caller;
+ */
+void wanpipe_queue_work (struct work_struct *work_pointer)
+{
+ if (test_and_set_bit(1, (void*)&wanpipe_bh_critical))
+ printk(KERN_INFO "CRITICAL IN QUEUING WORK\n");
+
+ queue_work(wanpipe_wq, work_pointer);
+ clear_bit(1,(void*)&wanpipe_bh_critical);
+}
+
+void wakeup_sk_bh(struct net_device *dev)
+{
+ wanpipe_common_t *chan = dev->priv;
+
+ if (test_bit(0,&chan->common_critical))
+ return;
+
+ if (chan->sk && chan->tx_timer){
+ chan->tx_timer->expires=jiffies+1;
+ add_timer(chan->tx_timer);
+ }
+}
+
+int change_dev_flags(struct net_device *dev, unsigned flags)
+{
+ struct ifreq if_info;
+ mm_segment_t fs = get_fs();
+ int err;
+
+ memset(&if_info, 0, sizeof(if_info));
+ strcpy(if_info.ifr_name, dev->name);
+ if_info.ifr_flags = flags;
+
+ set_fs(get_ds()); /* get user space block */
+ err = devinet_ioctl(SIOCSIFFLAGS, &if_info);
+ set_fs(fs);
+
+ return err;
+}
+
+unsigned long get_ip_address(struct net_device *dev, int option)
+{
+
+ struct in_ifaddr *ifaddr;
+ struct in_device *in_dev;
+
+ if ((in_dev = __in_dev_get(dev)) == NULL){
+ return 0;
+ }
+
+ if ((ifaddr = in_dev->ifa_list)== NULL ){
+ return 0;
+ }
+
+ switch (option){
+
+ case WAN_LOCAL_IP:
+ return ifaddr->ifa_local;
+ break;
+
+ case WAN_POINTOPOINT_IP:
+ return ifaddr->ifa_address;
+ break;
+
+ case WAN_NETMASK_IP:
+ return ifaddr->ifa_mask;
+ break;
+
+ case WAN_BROADCAST_IP:
+ return ifaddr->ifa_broadcast;
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+void add_gateway(sdla_t *card, struct net_device *dev)
+{
+ mm_segment_t oldfs;
+ struct rtentry route;
+ int res;
+
+ memset((char*)&route,0,sizeof(struct rtentry));
+
+ ((struct sockaddr_in *)
+ &(route.rt_dst))->sin_addr.s_addr = 0;
+ ((struct sockaddr_in *)
+ &(route.rt_dst))->sin_family = AF_INET;
+
+ ((struct sockaddr_in *)
+ &(route.rt_genmask))->sin_addr.s_addr = 0;
+ ((struct sockaddr_in *)
+ &(route.rt_genmask)) ->sin_family = AF_INET;
+
+
+ route.rt_flags = 0;
+ route.rt_dev = dev->name;
+
+ oldfs = get_fs();
+ set_fs(get_ds());
+ res = ip_rt_ioctl(SIOCADDRT,&route);
+ set_fs(oldfs);
+
+ if (res == 0){
+ printk(KERN_INFO "%s: Gateway added for %s\n",
+ card->devname,dev->name);
+ }
+
+ return;
+}
+
+MODULE_LICENSE("GPL");
+
+/****** End *********************************************************/
diff --git a/drivers/net/wan/sealevel.c b/drivers/net/wan/sealevel.c
new file mode 100644
index 00000000000..5380ddfcd7d
--- /dev/null
+++ b/drivers/net/wan/sealevel.c
@@ -0,0 +1,469 @@
+/*
+ * Sealevel Systems 4021 driver.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * (c) Copyright 1999, 2001 Alan Cox
+ * (c) Copyright 2001 Red Hat Inc.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <net/arp.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+#include <net/syncppp.h>
+#include "z85230.h"
+
+
+struct slvl_device
+{
+ void *if_ptr; /* General purpose pointer (used by SPPP) */
+ struct z8530_channel *chan;
+ struct ppp_device pppdev;
+ int channel;
+};
+
+
+struct slvl_board
+{
+ struct slvl_device *dev[2];
+ struct z8530_dev board;
+ int iobase;
+};
+
+/*
+ * Network driver support routines
+ */
+
+/*
+ * Frame receive. Simple for our card as we do sync ppp and there
+ * is no funny garbage involved
+ */
+
+static void sealevel_input(struct z8530_channel *c, struct sk_buff *skb)
+{
+ /* Drop the CRC - it's not a good idea to try and negotiate it ;) */
+ skb_trim(skb, skb->len-2);
+ skb->protocol=htons(ETH_P_WAN_PPP);
+ skb->mac.raw=skb->data;
+ skb->dev=c->netdevice;
+ /*
+ * Send it to the PPP layer. We don't have time to process
+ * it right now.
+ */
+ netif_rx(skb);
+ c->netdevice->last_rx = jiffies;
+}
+
+/*
+ * We've been placed in the UP state
+ */
+
+static int sealevel_open(struct net_device *d)
+{
+ struct slvl_device *slvl=d->priv;
+ int err = -1;
+ int unit = slvl->channel;
+
+ /*
+ * Link layer up.
+ */
+
+ switch(unit)
+ {
+ case 0:
+ err=z8530_sync_dma_open(d, slvl->chan);
+ break;
+ case 1:
+ err=z8530_sync_open(d, slvl->chan);
+ break;
+ }
+
+ if(err)
+ return err;
+ /*
+ * Begin PPP
+ */
+ err=sppp_open(d);
+ if(err)
+ {
+ switch(unit)
+ {
+ case 0:
+ z8530_sync_dma_close(d, slvl->chan);
+ break;
+ case 1:
+ z8530_sync_close(d, slvl->chan);
+ break;
+ }
+ return err;
+ }
+
+ slvl->chan->rx_function=sealevel_input;
+
+ /*
+ * Go go go
+ */
+ netif_start_queue(d);
+ return 0;
+}
+
+static int sealevel_close(struct net_device *d)
+{
+ struct slvl_device *slvl=d->priv;
+ int unit = slvl->channel;
+
+ /*
+ * Discard new frames
+ */
+
+ slvl->chan->rx_function=z8530_null_rx;
+
+ /*
+ * PPP off
+ */
+ sppp_close(d);
+ /*
+ * Link layer down
+ */
+
+ netif_stop_queue(d);
+
+ switch(unit)
+ {
+ case 0:
+ z8530_sync_dma_close(d, slvl->chan);
+ break;
+ case 1:
+ z8530_sync_close(d, slvl->chan);
+ break;
+ }
+ return 0;
+}
+
+static int sealevel_ioctl(struct net_device *d, struct ifreq *ifr, int cmd)
+{
+ /* struct slvl_device *slvl=d->priv;
+ z8530_ioctl(d,&slvl->sync.chanA,ifr,cmd) */
+ return sppp_do_ioctl(d, ifr,cmd);
+}
+
+static struct net_device_stats *sealevel_get_stats(struct net_device *d)
+{
+ struct slvl_device *slvl=d->priv;
+ if(slvl)
+ return z8530_get_stats(slvl->chan);
+ else
+ return NULL;
+}
+
+/*
+ * Passed PPP frames, fire them downwind.
+ */
+
+static int sealevel_queue_xmit(struct sk_buff *skb, struct net_device *d)
+{
+ struct slvl_device *slvl=d->priv;
+ return z8530_queue_xmit(slvl->chan, skb);
+}
+
+static int sealevel_neigh_setup(struct neighbour *n)
+{
+ if (n->nud_state == NUD_NONE) {
+ n->ops = &arp_broken_ops;
+ n->output = n->ops->output;
+ }
+ return 0;
+}
+
+static int sealevel_neigh_setup_dev(struct net_device *dev, struct neigh_parms *p)
+{
+ if (p->tbl->family == AF_INET) {
+ p->neigh_setup = sealevel_neigh_setup;
+ p->ucast_probes = 0;
+ p->mcast_probes = 0;
+ }
+ return 0;
+}
+
+static int sealevel_attach(struct net_device *dev)
+{
+ struct slvl_device *sv = dev->priv;
+ sppp_attach(&sv->pppdev);
+ return 0;
+}
+
+static void sealevel_detach(struct net_device *dev)
+{
+ sppp_detach(dev);
+}
+
+static void slvl_setup(struct net_device *d)
+{
+ d->open = sealevel_open;
+ d->stop = sealevel_close;
+ d->init = sealevel_attach;
+ d->uninit = sealevel_detach;
+ d->hard_start_xmit = sealevel_queue_xmit;
+ d->get_stats = sealevel_get_stats;
+ d->set_multicast_list = NULL;
+ d->do_ioctl = sealevel_ioctl;
+ d->neigh_setup = sealevel_neigh_setup_dev;
+ d->set_mac_address = NULL;
+
+}
+
+static inline struct slvl_device *slvl_alloc(int iobase, int irq)
+{
+ struct net_device *d;
+ struct slvl_device *sv;
+
+ d = alloc_netdev(sizeof(struct slvl_device), "hdlc%d",
+ slvl_setup);
+
+ if (!d)
+ return NULL;
+
+ sv = d->priv;
+ sv->if_ptr = &sv->pppdev;
+ sv->pppdev.dev = d;
+ d->base_addr = iobase;
+ d->irq = irq;
+
+ return sv;
+}
+
+
+/*
+ * Allocate and setup Sealevel board.
+ */
+
+static __init struct slvl_board *slvl_init(int iobase, int irq,
+ int txdma, int rxdma, int slow)
+{
+ struct z8530_dev *dev;
+ struct slvl_board *b;
+
+ /*
+ * Get the needed I/O space
+ */
+
+ if(!request_region(iobase, 8, "Sealevel 4021"))
+ {
+ printk(KERN_WARNING "sealevel: I/O 0x%X already in use.\n", iobase);
+ return NULL;
+ }
+
+ b = kmalloc(sizeof(struct slvl_board), GFP_KERNEL);
+ if(!b)
+ goto fail3;
+
+ memset(b, 0, sizeof(*b));
+ if (!(b->dev[0]= slvl_alloc(iobase, irq)))
+ goto fail2;
+
+ b->dev[0]->chan = &b->board.chanA;
+ b->dev[0]->channel = 0;
+
+ if (!(b->dev[1] = slvl_alloc(iobase, irq)))
+ goto fail1_0;
+
+ b->dev[1]->chan = &b->board.chanB;
+ b->dev[1]->channel = 1;
+
+ dev = &b->board;
+
+ /*
+ * Stuff in the I/O addressing
+ */
+
+ dev->active = 0;
+
+ b->iobase = iobase;
+
+ /*
+ * Select 8530 delays for the old board
+ */
+
+ if(slow)
+ iobase |= Z8530_PORT_SLEEP;
+
+ dev->chanA.ctrlio=iobase+1;
+ dev->chanA.dataio=iobase;
+ dev->chanB.ctrlio=iobase+3;
+ dev->chanB.dataio=iobase+2;
+
+ dev->chanA.irqs=&z8530_nop;
+ dev->chanB.irqs=&z8530_nop;
+
+ /*
+ * Assert DTR enable DMA
+ */
+
+ outb(3|(1<<7), b->iobase+4);
+
+
+ /* We want a fast IRQ for this device. Actually we'd like an even faster
+ IRQ ;) - This is one driver RtLinux is made for */
+
+ if(request_irq(irq, &z8530_interrupt, SA_INTERRUPT, "SeaLevel", dev)<0)
+ {
+ printk(KERN_WARNING "sealevel: IRQ %d already in use.\n", irq);
+ goto fail1_1;
+ }
+
+ dev->irq=irq;
+ dev->chanA.private=&b->dev[0];
+ dev->chanB.private=&b->dev[1];
+ dev->chanA.netdevice=b->dev[0]->pppdev.dev;
+ dev->chanB.netdevice=b->dev[1]->pppdev.dev;
+ dev->chanA.dev=dev;
+ dev->chanB.dev=dev;
+
+ dev->chanA.txdma=3;
+ dev->chanA.rxdma=1;
+ if(request_dma(dev->chanA.txdma, "SeaLevel (TX)")!=0)
+ goto fail;
+
+ if(request_dma(dev->chanA.rxdma, "SeaLevel (RX)")!=0)
+ goto dmafail;
+
+ disable_irq(irq);
+
+ /*
+ * Begin normal initialise
+ */
+
+ if(z8530_init(dev)!=0)
+ {
+ printk(KERN_ERR "Z8530 series device not found.\n");
+ enable_irq(irq);
+ goto dmafail2;
+ }
+ if(dev->type==Z85C30)
+ {
+ z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream);
+ z8530_channel_load(&dev->chanB, z8530_hdlc_kilostream);
+ }
+ else
+ {
+ z8530_channel_load(&dev->chanA, z8530_hdlc_kilostream_85230);
+ z8530_channel_load(&dev->chanB, z8530_hdlc_kilostream_85230);
+ }
+
+ /*
+ * Now we can take the IRQ
+ */
+
+ enable_irq(irq);
+
+ if (register_netdev(b->dev[0]->pppdev.dev))
+ goto dmafail2;
+
+ if (register_netdev(b->dev[1]->pppdev.dev))
+ goto fail_unit;
+
+ z8530_describe(dev, "I/O", iobase);
+ dev->active=1;
+ return b;
+
+fail_unit:
+ unregister_netdev(b->dev[0]->pppdev.dev);
+
+dmafail2:
+ free_dma(dev->chanA.rxdma);
+dmafail:
+ free_dma(dev->chanA.txdma);
+fail:
+ free_irq(irq, dev);
+fail1_1:
+ free_netdev(b->dev[1]->pppdev.dev);
+fail1_0:
+ free_netdev(b->dev[0]->pppdev.dev);
+fail2:
+ kfree(b);
+fail3:
+ release_region(iobase,8);
+ return NULL;
+}
+
+static void __exit slvl_shutdown(struct slvl_board *b)
+{
+ int u;
+
+ z8530_shutdown(&b->board);
+
+ for(u=0; u<2; u++)
+ {
+ struct net_device *d = b->dev[u]->pppdev.dev;
+ unregister_netdev(d);
+ free_netdev(d);
+ }
+
+ free_irq(b->board.irq, &b->board);
+ free_dma(b->board.chanA.rxdma);
+ free_dma(b->board.chanA.txdma);
+ /* DMA off on the card, drop DTR */
+ outb(0, b->iobase);
+ release_region(b->iobase, 8);
+ kfree(b);
+}
+
+
+static int io=0x238;
+static int txdma=1;
+static int rxdma=3;
+static int irq=5;
+static int slow=0;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "The I/O base of the Sealevel card");
+module_param(txdma, int, 0);
+MODULE_PARM_DESC(txdma, "Transmit DMA channel");
+module_param(rxdma, int, 0);
+MODULE_PARM_DESC(rxdma, "Receive DMA channel");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "The interrupt line setting for the SeaLevel card");
+module_param(slow, bool, 0);
+MODULE_PARM_DESC(slow, "Set this for an older Sealevel card such as the 4012");
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Modular driver for the SeaLevel 4021");
+
+static struct slvl_board *slvl_unit;
+
+static int __init slvl_init_module(void)
+{
+#ifdef MODULE
+ printk(KERN_INFO "SeaLevel Z85230 Synchronous Driver v 0.02.\n");
+ printk(KERN_INFO "(c) Copyright 1998, Building Number Three Ltd.\n");
+#endif
+ slvl_unit = slvl_init(io, irq, txdma, rxdma, slow);
+
+ return slvl_unit ? 0 : -ENODEV;
+}
+
+static void __exit slvl_cleanup_module(void)
+{
+ if(slvl_unit)
+ slvl_shutdown(slvl_unit);
+}
+
+module_init(slvl_init_module);
+module_exit(slvl_cleanup_module);
diff --git a/drivers/net/wan/syncppp.c b/drivers/net/wan/syncppp.c
new file mode 100644
index 00000000000..84b65c60c79
--- /dev/null
+++ b/drivers/net/wan/syncppp.c
@@ -0,0 +1,1488 @@
+/*
+ * NET3: A (fairly minimal) implementation of synchronous PPP for Linux
+ * as well as a CISCO HDLC implementation. See the copyright
+ * message below for the original source.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the license, or (at your option) any later version.
+ *
+ * Note however. This code is also used in a different form by FreeBSD.
+ * Therefore when making any non OS specific change please consider
+ * contributing it back to the original author under the terms
+ * below in addition.
+ * -- Alan
+ *
+ * Port for Linux-2.1 by Jan "Yenya" Kasprzak <kas@fi.muni.cz>
+ */
+
+/*
+ * Synchronous PPP/Cisco link level subroutines.
+ * Keepalive protocol implemented in both Cisco and PPP modes.
+ *
+ * Copyright (C) 1994 Cronyx Ltd.
+ * Author: Serge Vakulenko, <vak@zebub.msk.su>
+ *
+ * This software is distributed with NO WARRANTIES, not even the implied
+ * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Authors grant any other persons or organisations permission to use
+ * or modify this software as long as this message is kept with the software,
+ * all derivative works or modified versions.
+ *
+ * Version 1.9, Wed Oct 4 18:58:15 MSK 1995
+ *
+ * $Id: syncppp.c,v 1.18 2000/04/11 05:25:31 asj Exp $
+ */
+#undef DEBUG
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <linux/route.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/random.h>
+#include <linux/pkt_sched.h>
+#include <linux/spinlock.h>
+#include <linux/rcupdate.h>
+
+#include <net/syncppp.h>
+
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+
+#define MAXALIVECNT 6 /* max. alive packets */
+
+#define PPP_ALLSTATIONS 0xff /* All-Stations broadcast address */
+#define PPP_UI 0x03 /* Unnumbered Information */
+#define PPP_IP 0x0021 /* Internet Protocol */
+#define PPP_ISO 0x0023 /* ISO OSI Protocol */
+#define PPP_XNS 0x0025 /* Xerox NS Protocol */
+#define PPP_IPX 0x002b /* Novell IPX Protocol */
+#define PPP_LCP 0xc021 /* Link Control Protocol */
+#define PPP_IPCP 0x8021 /* Internet Protocol Control Protocol */
+
+#define LCP_CONF_REQ 1 /* PPP LCP configure request */
+#define LCP_CONF_ACK 2 /* PPP LCP configure acknowledge */
+#define LCP_CONF_NAK 3 /* PPP LCP configure negative ack */
+#define LCP_CONF_REJ 4 /* PPP LCP configure reject */
+#define LCP_TERM_REQ 5 /* PPP LCP terminate request */
+#define LCP_TERM_ACK 6 /* PPP LCP terminate acknowledge */
+#define LCP_CODE_REJ 7 /* PPP LCP code reject */
+#define LCP_PROTO_REJ 8 /* PPP LCP protocol reject */
+#define LCP_ECHO_REQ 9 /* PPP LCP echo request */
+#define LCP_ECHO_REPLY 10 /* PPP LCP echo reply */
+#define LCP_DISC_REQ 11 /* PPP LCP discard request */
+
+#define LCP_OPT_MRU 1 /* maximum receive unit */
+#define LCP_OPT_ASYNC_MAP 2 /* async control character map */
+#define LCP_OPT_AUTH_PROTO 3 /* authentication protocol */
+#define LCP_OPT_QUAL_PROTO 4 /* quality protocol */
+#define LCP_OPT_MAGIC 5 /* magic number */
+#define LCP_OPT_RESERVED 6 /* reserved */
+#define LCP_OPT_PROTO_COMP 7 /* protocol field compression */
+#define LCP_OPT_ADDR_COMP 8 /* address/control field compression */
+
+#define IPCP_CONF_REQ LCP_CONF_REQ /* PPP IPCP configure request */
+#define IPCP_CONF_ACK LCP_CONF_ACK /* PPP IPCP configure acknowledge */
+#define IPCP_CONF_NAK LCP_CONF_NAK /* PPP IPCP configure negative ack */
+#define IPCP_CONF_REJ LCP_CONF_REJ /* PPP IPCP configure reject */
+#define IPCP_TERM_REQ LCP_TERM_REQ /* PPP IPCP terminate request */
+#define IPCP_TERM_ACK LCP_TERM_ACK /* PPP IPCP terminate acknowledge */
+#define IPCP_CODE_REJ LCP_CODE_REJ /* PPP IPCP code reject */
+
+#define CISCO_MULTICAST 0x8f /* Cisco multicast address */
+#define CISCO_UNICAST 0x0f /* Cisco unicast address */
+#define CISCO_KEEPALIVE 0x8035 /* Cisco keepalive protocol */
+#define CISCO_ADDR_REQ 0 /* Cisco address request */
+#define CISCO_ADDR_REPLY 1 /* Cisco address reply */
+#define CISCO_KEEPALIVE_REQ 2 /* Cisco keepalive request */
+
+struct ppp_header {
+ u8 address;
+ u8 control;
+ u16 protocol;
+};
+#define PPP_HEADER_LEN sizeof (struct ppp_header)
+
+struct lcp_header {
+ u8 type;
+ u8 ident;
+ u16 len;
+};
+#define LCP_HEADER_LEN sizeof (struct lcp_header)
+
+struct cisco_packet {
+ u32 type;
+ u32 par1;
+ u32 par2;
+ u16 rel;
+ u16 time0;
+ u16 time1;
+};
+#define CISCO_PACKET_LEN 18
+#define CISCO_BIG_PACKET_LEN 20
+
+static struct sppp *spppq;
+static struct timer_list sppp_keepalive_timer;
+static DEFINE_SPINLOCK(spppq_lock);
+
+/* global xmit queue for sending packets while spinlock is held */
+static struct sk_buff_head tx_queue;
+
+static void sppp_keepalive (unsigned long dummy);
+static void sppp_cp_send (struct sppp *sp, u16 proto, u8 type,
+ u8 ident, u16 len, void *data);
+static void sppp_cisco_send (struct sppp *sp, int type, long par1, long par2);
+static void sppp_lcp_input (struct sppp *sp, struct sk_buff *m);
+static void sppp_cisco_input (struct sppp *sp, struct sk_buff *m);
+static void sppp_ipcp_input (struct sppp *sp, struct sk_buff *m);
+static void sppp_lcp_open (struct sppp *sp);
+static void sppp_ipcp_open (struct sppp *sp);
+static int sppp_lcp_conf_parse_options (struct sppp *sp, struct lcp_header *h,
+ int len, u32 *magic);
+static void sppp_cp_timeout (unsigned long arg);
+static char *sppp_lcp_type_name (u8 type);
+static char *sppp_ipcp_type_name (u8 type);
+static void sppp_print_bytes (u8 *p, u16 len);
+
+static int debug;
+
+/* Flush global outgoing packet queue to dev_queue_xmit().
+ *
+ * dev_queue_xmit() must be called with interrupts enabled
+ * which means it can't be called with spinlocks held.
+ * If a packet needs to be sent while a spinlock is held,
+ * then put the packet into tx_queue, and call sppp_flush_xmit()
+ * after spinlock is released.
+ */
+static void sppp_flush_xmit(void)
+{
+ struct sk_buff *skb;
+ while ((skb = skb_dequeue(&tx_queue)) != NULL)
+ dev_queue_xmit(skb);
+}
+
+/*
+ * Interface down stub
+ */
+
+static void if_down(struct net_device *dev)
+{
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+
+ sp->pp_link_state=SPPP_LINK_DOWN;
+}
+
+/*
+ * Timeout routine activations.
+ */
+
+static void sppp_set_timeout(struct sppp *p,int s)
+{
+ if (! (p->pp_flags & PP_TIMO))
+ {
+ init_timer(&p->pp_timer);
+ p->pp_timer.function=sppp_cp_timeout;
+ p->pp_timer.expires=jiffies+s*HZ;
+ p->pp_timer.data=(unsigned long)p;
+ p->pp_flags |= PP_TIMO;
+ add_timer(&p->pp_timer);
+ }
+}
+
+static void sppp_clear_timeout(struct sppp *p)
+{
+ if (p->pp_flags & PP_TIMO)
+ {
+ del_timer(&p->pp_timer);
+ p->pp_flags &= ~PP_TIMO;
+ }
+}
+
+/**
+ * sppp_input - receive and process a WAN PPP frame
+ * @skb: The buffer to process
+ * @dev: The device it arrived on
+ *
+ * This can be called directly by cards that do not have
+ * timing constraints but is normally called from the network layer
+ * after interrupt servicing to process frames queued via netif_rx().
+ *
+ * We process the options in the card. If the frame is destined for
+ * the protocol stacks then it requeues the frame for the upper level
+ * protocol. If it is a control from it is processed and discarded
+ * here.
+ */
+
+void sppp_input (struct net_device *dev, struct sk_buff *skb)
+{
+ struct ppp_header *h;
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+ unsigned long flags;
+
+ skb->dev=dev;
+ skb->mac.raw=skb->data;
+
+ if (dev->flags & IFF_RUNNING)
+ {
+ /* Count received bytes, add FCS and one flag */
+ sp->ibytes+= skb->len + 3;
+ sp->ipkts++;
+ }
+
+ if (!pskb_may_pull(skb, PPP_HEADER_LEN)) {
+ /* Too small packet, drop it. */
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_DEBUG "%s: input packet is too small, %d bytes\n",
+ dev->name, skb->len);
+ kfree_skb(skb);
+ return;
+ }
+
+ /* Get PPP header. */
+ h = (struct ppp_header *)skb->data;
+ skb_pull(skb,sizeof(struct ppp_header));
+
+ spin_lock_irqsave(&sp->lock, flags);
+
+ switch (h->address) {
+ default: /* Invalid PPP packet. */
+ goto invalid;
+ case PPP_ALLSTATIONS:
+ if (h->control != PPP_UI)
+ goto invalid;
+ if (sp->pp_flags & PP_CISCO) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: PPP packet in Cisco mode <0x%x 0x%x 0x%x>\n",
+ dev->name,
+ h->address, h->control, ntohs (h->protocol));
+ goto drop;
+ }
+ switch (ntohs (h->protocol)) {
+ default:
+ if (sp->lcp.state == LCP_STATE_OPENED)
+ sppp_cp_send (sp, PPP_LCP, LCP_PROTO_REJ,
+ ++sp->pp_seq, skb->len + 2,
+ &h->protocol);
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid input protocol <0x%x 0x%x 0x%x>\n",
+ dev->name,
+ h->address, h->control, ntohs (h->protocol));
+ goto drop;
+ case PPP_LCP:
+ sppp_lcp_input (sp, skb);
+ goto drop;
+ case PPP_IPCP:
+ if (sp->lcp.state == LCP_STATE_OPENED)
+ sppp_ipcp_input (sp, skb);
+ else
+ printk(KERN_DEBUG "IPCP when still waiting LCP finish.\n");
+ goto drop;
+ case PPP_IP:
+ if (sp->ipcp.state == IPCP_STATE_OPENED) {
+ if(sp->pp_flags&PP_DEBUG)
+ printk(KERN_DEBUG "Yow an IP frame.\n");
+ skb->protocol=htons(ETH_P_IP);
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ goto done;
+ }
+ break;
+#ifdef IPX
+ case PPP_IPX:
+ /* IPX IPXCP not implemented yet */
+ if (sp->lcp.state == LCP_STATE_OPENED) {
+ skb->protocol=htons(ETH_P_IPX);
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ goto done;
+ }
+ break;
+#endif
+ }
+ break;
+ case CISCO_MULTICAST:
+ case CISCO_UNICAST:
+ /* Don't check the control field here (RFC 1547). */
+ if (! (sp->pp_flags & PP_CISCO)) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: Cisco packet in PPP mode <0x%x 0x%x 0x%x>\n",
+ dev->name,
+ h->address, h->control, ntohs (h->protocol));
+ goto drop;
+ }
+ switch (ntohs (h->protocol)) {
+ default:
+ goto invalid;
+ case CISCO_KEEPALIVE:
+ sppp_cisco_input (sp, skb);
+ goto drop;
+#ifdef CONFIG_INET
+ case ETH_P_IP:
+ skb->protocol=htons(ETH_P_IP);
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ goto done;
+#endif
+#ifdef CONFIG_IPX
+ case ETH_P_IPX:
+ skb->protocol=htons(ETH_P_IPX);
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ goto done;
+#endif
+ }
+ break;
+ }
+ goto drop;
+
+invalid:
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid input packet <0x%x 0x%x 0x%x>\n",
+ dev->name, h->address, h->control, ntohs (h->protocol));
+drop:
+ kfree_skb(skb);
+done:
+ spin_unlock_irqrestore(&sp->lock, flags);
+ sppp_flush_xmit();
+ return;
+}
+
+EXPORT_SYMBOL(sppp_input);
+
+/*
+ * Handle transmit packets.
+ */
+
+static int sppp_hard_header(struct sk_buff *skb, struct net_device *dev, __u16 type,
+ void *daddr, void *saddr, unsigned int len)
+{
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+ struct ppp_header *h;
+ skb_push(skb,sizeof(struct ppp_header));
+ h=(struct ppp_header *)skb->data;
+ if(sp->pp_flags&PP_CISCO)
+ {
+ h->address = CISCO_UNICAST;
+ h->control = 0;
+ }
+ else
+ {
+ h->address = PPP_ALLSTATIONS;
+ h->control = PPP_UI;
+ }
+ if(sp->pp_flags & PP_CISCO)
+ {
+ h->protocol = htons(type);
+ }
+ else switch(type)
+ {
+ case ETH_P_IP:
+ h->protocol = htons(PPP_IP);
+ break;
+ case ETH_P_IPX:
+ h->protocol = htons(PPP_IPX);
+ break;
+ }
+ return sizeof(struct ppp_header);
+}
+
+static int sppp_rebuild_header(struct sk_buff *skb)
+{
+ return 0;
+}
+
+/*
+ * Send keepalive packets, every 10 seconds.
+ */
+
+static void sppp_keepalive (unsigned long dummy)
+{
+ struct sppp *sp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&spppq_lock, flags);
+
+ for (sp=spppq; sp; sp=sp->pp_next)
+ {
+ struct net_device *dev = sp->pp_if;
+
+ /* Keepalive mode disabled or channel down? */
+ if (! (sp->pp_flags & PP_KEEPALIVE) ||
+ ! (dev->flags & IFF_UP))
+ continue;
+
+ spin_lock(&sp->lock);
+
+ /* No keepalive in PPP mode if LCP not opened yet. */
+ if (! (sp->pp_flags & PP_CISCO) &&
+ sp->lcp.state != LCP_STATE_OPENED) {
+ spin_unlock(&sp->lock);
+ continue;
+ }
+
+ if (sp->pp_alivecnt == MAXALIVECNT) {
+ /* No keepalive packets got. Stop the interface. */
+ printk (KERN_WARNING "%s: protocol down\n", dev->name);
+ if_down (dev);
+ if (! (sp->pp_flags & PP_CISCO)) {
+ /* Shut down the PPP link. */
+ sp->lcp.magic = jiffies;
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ sppp_clear_timeout (sp);
+ /* Initiate negotiation. */
+ sppp_lcp_open (sp);
+ }
+ }
+ if (sp->pp_alivecnt <= MAXALIVECNT)
+ ++sp->pp_alivecnt;
+ if (sp->pp_flags & PP_CISCO)
+ sppp_cisco_send (sp, CISCO_KEEPALIVE_REQ, ++sp->pp_seq,
+ sp->pp_rseq);
+ else if (sp->lcp.state == LCP_STATE_OPENED) {
+ long nmagic = htonl (sp->lcp.magic);
+ sp->lcp.echoid = ++sp->pp_seq;
+ sppp_cp_send (sp, PPP_LCP, LCP_ECHO_REQ,
+ sp->lcp.echoid, 4, &nmagic);
+ }
+
+ spin_unlock(&sp->lock);
+ }
+ spin_unlock_irqrestore(&spppq_lock, flags);
+ sppp_flush_xmit();
+ sppp_keepalive_timer.expires=jiffies+10*HZ;
+ add_timer(&sppp_keepalive_timer);
+}
+
+/*
+ * Handle incoming PPP Link Control Protocol packets.
+ */
+
+static void sppp_lcp_input (struct sppp *sp, struct sk_buff *skb)
+{
+ struct lcp_header *h;
+ struct net_device *dev = sp->pp_if;
+ int len = skb->len;
+ u8 *p, opt[6];
+ u32 rmagic;
+
+ if (!pskb_may_pull(skb, sizeof(struct lcp_header))) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid lcp packet length: %d bytes\n",
+ dev->name, len);
+ return;
+ }
+ h = (struct lcp_header *)skb->data;
+ skb_pull(skb,sizeof(struct lcp_header *));
+
+ if (sp->pp_flags & PP_DEBUG)
+ {
+ char state = '?';
+ switch (sp->lcp.state) {
+ case LCP_STATE_CLOSED: state = 'C'; break;
+ case LCP_STATE_ACK_RCVD: state = 'R'; break;
+ case LCP_STATE_ACK_SENT: state = 'S'; break;
+ case LCP_STATE_OPENED: state = 'O'; break;
+ }
+ printk (KERN_WARNING "%s: lcp input(%c): %d bytes <%s id=%xh len=%xh",
+ dev->name, state, len,
+ sppp_lcp_type_name (h->type), h->ident, ntohs (h->len));
+ if (len > 4)
+ sppp_print_bytes ((u8*) (h+1), len-4);
+ printk (">\n");
+ }
+ if (len > ntohs (h->len))
+ len = ntohs (h->len);
+ switch (h->type) {
+ default:
+ /* Unknown packet type -- send Code-Reject packet. */
+ sppp_cp_send (sp, PPP_LCP, LCP_CODE_REJ, ++sp->pp_seq,
+ skb->len, h);
+ break;
+ case LCP_CONF_REQ:
+ if (len < 4) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_DEBUG"%s: invalid lcp configure request packet length: %d bytes\n",
+ dev->name, len);
+ break;
+ }
+ if (len>4 && !sppp_lcp_conf_parse_options (sp, h, len, &rmagic))
+ goto badreq;
+ if (rmagic == sp->lcp.magic) {
+ /* Local and remote magics equal -- loopback? */
+ if (sp->pp_loopcnt >= MAXALIVECNT*5) {
+ printk (KERN_WARNING "%s: loopback\n",
+ dev->name);
+ sp->pp_loopcnt = 0;
+ if (dev->flags & IFF_UP) {
+ if_down (dev);
+ }
+ } else if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_DEBUG "%s: conf req: magic glitch\n",
+ dev->name);
+ ++sp->pp_loopcnt;
+
+ /* MUST send Conf-Nack packet. */
+ rmagic = ~sp->lcp.magic;
+ opt[0] = LCP_OPT_MAGIC;
+ opt[1] = sizeof (opt);
+ opt[2] = rmagic >> 24;
+ opt[3] = rmagic >> 16;
+ opt[4] = rmagic >> 8;
+ opt[5] = rmagic;
+ sppp_cp_send (sp, PPP_LCP, LCP_CONF_NAK,
+ h->ident, sizeof (opt), &opt);
+badreq:
+ switch (sp->lcp.state) {
+ case LCP_STATE_OPENED:
+ /* Initiate renegotiation. */
+ sppp_lcp_open (sp);
+ /* fall through... */
+ case LCP_STATE_ACK_SENT:
+ /* Go to closed state. */
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ }
+ break;
+ }
+ /* Send Configure-Ack packet. */
+ sp->pp_loopcnt = 0;
+ if (sp->lcp.state != LCP_STATE_OPENED) {
+ sppp_cp_send (sp, PPP_LCP, LCP_CONF_ACK,
+ h->ident, len-4, h+1);
+ }
+ /* Change the state. */
+ switch (sp->lcp.state) {
+ case LCP_STATE_CLOSED:
+ sp->lcp.state = LCP_STATE_ACK_SENT;
+ break;
+ case LCP_STATE_ACK_RCVD:
+ sp->lcp.state = LCP_STATE_OPENED;
+ sppp_ipcp_open (sp);
+ break;
+ case LCP_STATE_OPENED:
+ /* Remote magic changed -- close session. */
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ /* Initiate renegotiation. */
+ sppp_lcp_open (sp);
+ /* Send ACK after our REQ in attempt to break loop */
+ sppp_cp_send (sp, PPP_LCP, LCP_CONF_ACK,
+ h->ident, len-4, h+1);
+ sp->lcp.state = LCP_STATE_ACK_SENT;
+ break;
+ }
+ break;
+ case LCP_CONF_ACK:
+ if (h->ident != sp->lcp.confid)
+ break;
+ sppp_clear_timeout (sp);
+ if ((sp->pp_link_state != SPPP_LINK_UP) &&
+ (dev->flags & IFF_UP)) {
+ /* Coming out of loopback mode. */
+ sp->pp_link_state=SPPP_LINK_UP;
+ printk (KERN_INFO "%s: protocol up\n", dev->name);
+ }
+ switch (sp->lcp.state) {
+ case LCP_STATE_CLOSED:
+ sp->lcp.state = LCP_STATE_ACK_RCVD;
+ sppp_set_timeout (sp, 5);
+ break;
+ case LCP_STATE_ACK_SENT:
+ sp->lcp.state = LCP_STATE_OPENED;
+ sppp_ipcp_open (sp);
+ break;
+ }
+ break;
+ case LCP_CONF_NAK:
+ if (h->ident != sp->lcp.confid)
+ break;
+ p = (u8*) (h+1);
+ if (len>=10 && p[0] == LCP_OPT_MAGIC && p[1] >= 4) {
+ rmagic = (u32)p[2] << 24 |
+ (u32)p[3] << 16 | p[4] << 8 | p[5];
+ if (rmagic == ~sp->lcp.magic) {
+ int newmagic;
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_DEBUG "%s: conf nak: magic glitch\n",
+ dev->name);
+ get_random_bytes(&newmagic, sizeof(newmagic));
+ sp->lcp.magic += newmagic;
+ } else
+ sp->lcp.magic = rmagic;
+ }
+ if (sp->lcp.state != LCP_STATE_ACK_SENT) {
+ /* Go to closed state. */
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ }
+ /* The link will be renegotiated after timeout,
+ * to avoid endless req-nack loop. */
+ sppp_clear_timeout (sp);
+ sppp_set_timeout (sp, 2);
+ break;
+ case LCP_CONF_REJ:
+ if (h->ident != sp->lcp.confid)
+ break;
+ sppp_clear_timeout (sp);
+ /* Initiate renegotiation. */
+ sppp_lcp_open (sp);
+ if (sp->lcp.state != LCP_STATE_ACK_SENT) {
+ /* Go to closed state. */
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ }
+ break;
+ case LCP_TERM_REQ:
+ sppp_clear_timeout (sp);
+ /* Send Terminate-Ack packet. */
+ sppp_cp_send (sp, PPP_LCP, LCP_TERM_ACK, h->ident, 0, NULL);
+ /* Go to closed state. */
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ /* Initiate renegotiation. */
+ sppp_lcp_open (sp);
+ break;
+ case LCP_TERM_ACK:
+ case LCP_CODE_REJ:
+ case LCP_PROTO_REJ:
+ /* Ignore for now. */
+ break;
+ case LCP_DISC_REQ:
+ /* Discard the packet. */
+ break;
+ case LCP_ECHO_REQ:
+ if (sp->lcp.state != LCP_STATE_OPENED)
+ break;
+ if (len < 8) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid lcp echo request packet length: %d bytes\n",
+ dev->name, len);
+ break;
+ }
+ if (ntohl (*(long*)(h+1)) == sp->lcp.magic) {
+ /* Line loopback mode detected. */
+ printk (KERN_WARNING "%s: loopback\n", dev->name);
+ if_down (dev);
+
+ /* Shut down the PPP link. */
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ sppp_clear_timeout (sp);
+ /* Initiate negotiation. */
+ sppp_lcp_open (sp);
+ break;
+ }
+ *(long*)(h+1) = htonl (sp->lcp.magic);
+ sppp_cp_send (sp, PPP_LCP, LCP_ECHO_REPLY, h->ident, len-4, h+1);
+ break;
+ case LCP_ECHO_REPLY:
+ if (h->ident != sp->lcp.echoid)
+ break;
+ if (len < 8) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid lcp echo reply packet length: %d bytes\n",
+ dev->name, len);
+ break;
+ }
+ if (ntohl (*(long*)(h+1)) != sp->lcp.magic)
+ sp->pp_alivecnt = 0;
+ break;
+ }
+}
+
+/*
+ * Handle incoming Cisco keepalive protocol packets.
+ */
+
+static void sppp_cisco_input (struct sppp *sp, struct sk_buff *skb)
+{
+ struct cisco_packet *h;
+ struct net_device *dev = sp->pp_if;
+
+ if (!pskb_may_pull(skb, sizeof(struct cisco_packet))
+ || (skb->len != CISCO_PACKET_LEN
+ && skb->len != CISCO_BIG_PACKET_LEN)) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid cisco packet length: %d bytes\n",
+ dev->name, skb->len);
+ return;
+ }
+ h = (struct cisco_packet *)skb->data;
+ skb_pull(skb, sizeof(struct cisco_packet*));
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: cisco input: %d bytes <%xh %xh %xh %xh %xh-%xh>\n",
+ dev->name, skb->len,
+ ntohl (h->type), h->par1, h->par2, h->rel,
+ h->time0, h->time1);
+ switch (ntohl (h->type)) {
+ default:
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: unknown cisco packet type: 0x%x\n",
+ dev->name, ntohl (h->type));
+ break;
+ case CISCO_ADDR_REPLY:
+ /* Reply on address request, ignore */
+ break;
+ case CISCO_KEEPALIVE_REQ:
+ sp->pp_alivecnt = 0;
+ sp->pp_rseq = ntohl (h->par1);
+ if (sp->pp_seq == sp->pp_rseq) {
+ /* Local and remote sequence numbers are equal.
+ * Probably, the line is in loopback mode. */
+ int newseq;
+ if (sp->pp_loopcnt >= MAXALIVECNT) {
+ printk (KERN_WARNING "%s: loopback\n",
+ dev->name);
+ sp->pp_loopcnt = 0;
+ if (dev->flags & IFF_UP) {
+ if_down (dev);
+ }
+ }
+ ++sp->pp_loopcnt;
+
+ /* Generate new local sequence number */
+ get_random_bytes(&newseq, sizeof(newseq));
+ sp->pp_seq ^= newseq;
+ break;
+ }
+ sp->pp_loopcnt = 0;
+ if (sp->pp_link_state==SPPP_LINK_DOWN &&
+ (dev->flags & IFF_UP)) {
+ sp->pp_link_state=SPPP_LINK_UP;
+ printk (KERN_INFO "%s: protocol up\n", dev->name);
+ }
+ break;
+ case CISCO_ADDR_REQ:
+ /* Stolen from net/ipv4/devinet.c -- SIOCGIFADDR ioctl */
+ {
+ struct in_device *in_dev;
+ struct in_ifaddr *ifa;
+ u32 addr = 0, mask = ~0; /* FIXME: is the mask correct? */
+#ifdef CONFIG_INET
+ rcu_read_lock();
+ if ((in_dev = __in_dev_get(dev)) != NULL)
+ {
+ for (ifa=in_dev->ifa_list; ifa != NULL;
+ ifa=ifa->ifa_next) {
+ if (strcmp(dev->name, ifa->ifa_label) == 0)
+ {
+ addr = ifa->ifa_local;
+ mask = ifa->ifa_mask;
+ break;
+ }
+ }
+ }
+ rcu_read_unlock();
+#endif
+ /* I hope both addr and mask are in the net order */
+ sppp_cisco_send (sp, CISCO_ADDR_REPLY, addr, mask);
+ break;
+ }
+ }
+}
+
+
+/*
+ * Send PPP LCP packet.
+ */
+
+static void sppp_cp_send (struct sppp *sp, u16 proto, u8 type,
+ u8 ident, u16 len, void *data)
+{
+ struct ppp_header *h;
+ struct lcp_header *lh;
+ struct sk_buff *skb;
+ struct net_device *dev = sp->pp_if;
+
+ skb=alloc_skb(dev->hard_header_len+PPP_HEADER_LEN+LCP_HEADER_LEN+len,
+ GFP_ATOMIC);
+ if (skb==NULL)
+ return;
+
+ skb_reserve(skb,dev->hard_header_len);
+
+ h = (struct ppp_header *)skb_put(skb, sizeof(struct ppp_header));
+ h->address = PPP_ALLSTATIONS; /* broadcast address */
+ h->control = PPP_UI; /* Unnumbered Info */
+ h->protocol = htons (proto); /* Link Control Protocol */
+
+ lh = (struct lcp_header *)skb_put(skb, sizeof(struct lcp_header));
+ lh->type = type;
+ lh->ident = ident;
+ lh->len = htons (LCP_HEADER_LEN + len);
+
+ if (len)
+ memcpy(skb_put(skb,len),data, len);
+
+ if (sp->pp_flags & PP_DEBUG) {
+ printk (KERN_WARNING "%s: %s output <%s id=%xh len=%xh",
+ dev->name,
+ proto==PPP_LCP ? "lcp" : "ipcp",
+ proto==PPP_LCP ? sppp_lcp_type_name (lh->type) :
+ sppp_ipcp_type_name (lh->type), lh->ident,
+ ntohs (lh->len));
+ if (len)
+ sppp_print_bytes ((u8*) (lh+1), len);
+ printk (">\n");
+ }
+ sp->obytes += skb->len;
+ /* Control is high priority so it doesn't get queued behind data */
+ skb->priority=TC_PRIO_CONTROL;
+ skb->dev = dev;
+ skb_queue_tail(&tx_queue, skb);
+}
+
+/*
+ * Send Cisco keepalive packet.
+ */
+
+static void sppp_cisco_send (struct sppp *sp, int type, long par1, long par2)
+{
+ struct ppp_header *h;
+ struct cisco_packet *ch;
+ struct sk_buff *skb;
+ struct net_device *dev = sp->pp_if;
+ u32 t = jiffies * 1000/HZ;
+
+ skb=alloc_skb(dev->hard_header_len+PPP_HEADER_LEN+CISCO_PACKET_LEN,
+ GFP_ATOMIC);
+
+ if(skb==NULL)
+ return;
+
+ skb_reserve(skb, dev->hard_header_len);
+ h = (struct ppp_header *)skb_put (skb, sizeof(struct ppp_header));
+ h->address = CISCO_MULTICAST;
+ h->control = 0;
+ h->protocol = htons (CISCO_KEEPALIVE);
+
+ ch = (struct cisco_packet*)skb_put(skb, CISCO_PACKET_LEN);
+ ch->type = htonl (type);
+ ch->par1 = htonl (par1);
+ ch->par2 = htonl (par2);
+ ch->rel = -1;
+ ch->time0 = htons ((u16) (t >> 16));
+ ch->time1 = htons ((u16) t);
+
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: cisco output: <%xh %xh %xh %xh %xh-%xh>\n",
+ dev->name, ntohl (ch->type), ch->par1,
+ ch->par2, ch->rel, ch->time0, ch->time1);
+ sp->obytes += skb->len;
+ skb->priority=TC_PRIO_CONTROL;
+ skb->dev = dev;
+ skb_queue_tail(&tx_queue, skb);
+}
+
+/**
+ * sppp_close - close down a synchronous PPP or Cisco HDLC link
+ * @dev: The network device to drop the link of
+ *
+ * This drops the logical interface to the channel. It is not
+ * done politely as we assume we will also be dropping DTR. Any
+ * timeouts are killed.
+ */
+
+int sppp_close (struct net_device *dev)
+{
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sp->lock, flags);
+ sp->pp_link_state = SPPP_LINK_DOWN;
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ sppp_clear_timeout (sp);
+ spin_unlock_irqrestore(&sp->lock, flags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(sppp_close);
+
+/**
+ * sppp_open - open a synchronous PPP or Cisco HDLC link
+ * @dev: Network device to activate
+ *
+ * Close down any existing synchronous session and commence
+ * from scratch. In the PPP case this means negotiating LCP/IPCP
+ * and friends, while for Cisco HDLC we simply need to start sending
+ * keepalives
+ */
+
+int sppp_open (struct net_device *dev)
+{
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+ unsigned long flags;
+
+ sppp_close(dev);
+
+ spin_lock_irqsave(&sp->lock, flags);
+ if (!(sp->pp_flags & PP_CISCO)) {
+ sppp_lcp_open (sp);
+ }
+ sp->pp_link_state = SPPP_LINK_DOWN;
+ spin_unlock_irqrestore(&sp->lock, flags);
+ sppp_flush_xmit();
+
+ return 0;
+}
+
+EXPORT_SYMBOL(sppp_open);
+
+/**
+ * sppp_reopen - notify of physical link loss
+ * @dev: Device that lost the link
+ *
+ * This function informs the synchronous protocol code that
+ * the underlying link died (for example a carrier drop on X.21)
+ *
+ * We increment the magic numbers to ensure that if the other end
+ * failed to notice we will correctly start a new session. It happens
+ * do to the nature of telco circuits is that you can lose carrier on
+ * one endonly.
+ *
+ * Having done this we go back to negotiating. This function may
+ * be called from an interrupt context.
+ */
+
+int sppp_reopen (struct net_device *dev)
+{
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+ unsigned long flags;
+
+ sppp_close(dev);
+
+ spin_lock_irqsave(&sp->lock, flags);
+ if (!(sp->pp_flags & PP_CISCO))
+ {
+ sp->lcp.magic = jiffies;
+ ++sp->pp_seq;
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ /* Give it a moment for the line to settle then go */
+ sppp_set_timeout (sp, 1);
+ }
+ sp->pp_link_state=SPPP_LINK_DOWN;
+ spin_unlock_irqrestore(&sp->lock, flags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(sppp_reopen);
+
+/**
+ * sppp_change_mtu - Change the link MTU
+ * @dev: Device to change MTU on
+ * @new_mtu: New MTU
+ *
+ * Change the MTU on the link. This can only be called with
+ * the link down. It returns an error if the link is up or
+ * the mtu is out of range.
+ */
+
+int sppp_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if(new_mtu<128||new_mtu>PPP_MTU||(dev->flags&IFF_UP))
+ return -EINVAL;
+ dev->mtu=new_mtu;
+ return 0;
+}
+
+EXPORT_SYMBOL(sppp_change_mtu);
+
+/**
+ * sppp_do_ioctl - Ioctl handler for ppp/hdlc
+ * @dev: Device subject to ioctl
+ * @ifr: Interface request block from the user
+ * @cmd: Command that is being issued
+ *
+ * This function handles the ioctls that may be issued by the user
+ * to control the settings of a PPP/HDLC link. It does both busy
+ * and security checks. This function is intended to be wrapped by
+ * callers who wish to add additional ioctl calls of their own.
+ */
+
+int sppp_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct sppp *sp = (struct sppp *)sppp_of(dev);
+
+ if(dev->flags&IFF_UP)
+ return -EBUSY;
+
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch(cmd)
+ {
+ case SPPPIOCCISCO:
+ sp->pp_flags|=PP_CISCO;
+ dev->type = ARPHRD_HDLC;
+ break;
+ case SPPPIOCPPP:
+ sp->pp_flags&=~PP_CISCO;
+ dev->type = ARPHRD_PPP;
+ break;
+ case SPPPIOCDEBUG:
+ sp->pp_flags&=~PP_DEBUG;
+ if(ifr->ifr_flags)
+ sp->pp_flags|=PP_DEBUG;
+ break;
+ case SPPPIOCGFLAGS:
+ if(copy_to_user(ifr->ifr_data, &sp->pp_flags, sizeof(sp->pp_flags)))
+ return -EFAULT;
+ break;
+ case SPPPIOCSFLAGS:
+ if(copy_from_user(&sp->pp_flags, ifr->ifr_data, sizeof(sp->pp_flags)))
+ return -EFAULT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(sppp_do_ioctl);
+
+/**
+ * sppp_attach - attach synchronous PPP/HDLC to a device
+ * @pd: PPP device to initialise
+ *
+ * This initialises the PPP/HDLC support on an interface. At the
+ * time of calling the dev element must point to the network device
+ * that this interface is attached to. The interface should not yet
+ * be registered.
+ */
+
+void sppp_attach(struct ppp_device *pd)
+{
+ struct net_device *dev = pd->dev;
+ struct sppp *sp = &pd->sppp;
+ unsigned long flags;
+
+ /* Make sure embedding is safe for sppp_of */
+ BUG_ON(sppp_of(dev) != sp);
+
+ spin_lock_irqsave(&spppq_lock, flags);
+ /* Initialize keepalive handler. */
+ if (! spppq)
+ {
+ init_timer(&sppp_keepalive_timer);
+ sppp_keepalive_timer.expires=jiffies+10*HZ;
+ sppp_keepalive_timer.function=sppp_keepalive;
+ add_timer(&sppp_keepalive_timer);
+ }
+ /* Insert new entry into the keepalive list. */
+ sp->pp_next = spppq;
+ spppq = sp;
+ spin_unlock_irqrestore(&spppq_lock, flags);
+
+ sp->pp_loopcnt = 0;
+ sp->pp_alivecnt = 0;
+ sp->pp_seq = 0;
+ sp->pp_rseq = 0;
+ sp->pp_flags = PP_KEEPALIVE|PP_CISCO|debug;/*PP_DEBUG;*/
+ sp->lcp.magic = 0;
+ sp->lcp.state = LCP_STATE_CLOSED;
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ sp->pp_if = dev;
+ spin_lock_init(&sp->lock);
+
+ /*
+ * Device specific setup. All but interrupt handler and
+ * hard_start_xmit.
+ */
+
+ dev->hard_header = sppp_hard_header;
+ dev->rebuild_header = sppp_rebuild_header;
+ dev->tx_queue_len = 10;
+ dev->type = ARPHRD_HDLC;
+ dev->addr_len = 0;
+ dev->hard_header_len = sizeof(struct ppp_header);
+ dev->mtu = PPP_MTU;
+ /*
+ * These 4 are callers but MUST also call sppp_ functions
+ */
+ dev->do_ioctl = sppp_do_ioctl;
+#if 0
+ dev->get_stats = NULL; /* Let the driver override these */
+ dev->open = sppp_open;
+ dev->stop = sppp_close;
+#endif
+ dev->change_mtu = sppp_change_mtu;
+ dev->hard_header_cache = NULL;
+ dev->header_cache_update = NULL;
+ dev->flags = IFF_MULTICAST|IFF_POINTOPOINT|IFF_NOARP;
+}
+
+EXPORT_SYMBOL(sppp_attach);
+
+/**
+ * sppp_detach - release PPP resources from a device
+ * @dev: Network device to release
+ *
+ * Stop and free up any PPP/HDLC resources used by this
+ * interface. This must be called before the device is
+ * freed.
+ */
+
+void sppp_detach (struct net_device *dev)
+{
+ struct sppp **q, *p, *sp = (struct sppp *)sppp_of(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&spppq_lock, flags);
+ /* Remove the entry from the keepalive list. */
+ for (q = &spppq; (p = *q); q = &p->pp_next)
+ if (p == sp) {
+ *q = p->pp_next;
+ break;
+ }
+
+ /* Stop keepalive handler. */
+ if (! spppq)
+ del_timer(&sppp_keepalive_timer);
+ sppp_clear_timeout (sp);
+ spin_unlock_irqrestore(&spppq_lock, flags);
+}
+
+EXPORT_SYMBOL(sppp_detach);
+
+/*
+ * Analyze the LCP Configure-Request options list
+ * for the presence of unknown options.
+ * If the request contains unknown options, build and
+ * send Configure-reject packet, containing only unknown options.
+ */
+static int
+sppp_lcp_conf_parse_options (struct sppp *sp, struct lcp_header *h,
+ int len, u32 *magic)
+{
+ u8 *buf, *r, *p;
+ int rlen;
+
+ len -= 4;
+ buf = r = kmalloc (len, GFP_ATOMIC);
+ if (! buf)
+ return (0);
+
+ p = (void*) (h+1);
+ for (rlen=0; len>1 && p[1]; len-=p[1], p+=p[1]) {
+ switch (*p) {
+ case LCP_OPT_MAGIC:
+ /* Magic number -- extract. */
+ if (len >= 6 && p[1] == 6) {
+ *magic = (u32)p[2] << 24 |
+ (u32)p[3] << 16 | p[4] << 8 | p[5];
+ continue;
+ }
+ break;
+ case LCP_OPT_ASYNC_MAP:
+ /* Async control character map -- check to be zero. */
+ if (len >= 6 && p[1] == 6 && ! p[2] && ! p[3] &&
+ ! p[4] && ! p[5])
+ continue;
+ break;
+ case LCP_OPT_MRU:
+ /* Maximum receive unit -- always OK. */
+ continue;
+ default:
+ /* Others not supported. */
+ break;
+ }
+ /* Add the option to rejected list. */
+ memcpy(r, p, p[1]);
+ r += p[1];
+ rlen += p[1];
+ }
+ if (rlen)
+ sppp_cp_send (sp, PPP_LCP, LCP_CONF_REJ, h->ident, rlen, buf);
+ kfree(buf);
+ return (rlen == 0);
+}
+
+static void sppp_ipcp_input (struct sppp *sp, struct sk_buff *skb)
+{
+ struct lcp_header *h;
+ struct net_device *dev = sp->pp_if;
+ int len = skb->len;
+
+ if (!pskb_may_pull(skb, sizeof(struct lcp_header))) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid ipcp packet length: %d bytes\n",
+ dev->name, len);
+ return;
+ }
+ h = (struct lcp_header *)skb->data;
+ skb_pull(skb,sizeof(struct lcp_header));
+ if (sp->pp_flags & PP_DEBUG) {
+ printk (KERN_WARNING "%s: ipcp input: %d bytes <%s id=%xh len=%xh",
+ dev->name, len,
+ sppp_ipcp_type_name (h->type), h->ident, ntohs (h->len));
+ if (len > 4)
+ sppp_print_bytes ((u8*) (h+1), len-4);
+ printk (">\n");
+ }
+ if (len > ntohs (h->len))
+ len = ntohs (h->len);
+ switch (h->type) {
+ default:
+ /* Unknown packet type -- send Code-Reject packet. */
+ sppp_cp_send (sp, PPP_IPCP, IPCP_CODE_REJ, ++sp->pp_seq, len, h);
+ break;
+ case IPCP_CONF_REQ:
+ if (len < 4) {
+ if (sp->pp_flags & PP_DEBUG)
+ printk (KERN_WARNING "%s: invalid ipcp configure request packet length: %d bytes\n",
+ dev->name, len);
+ return;
+ }
+ if (len > 4) {
+ sppp_cp_send (sp, PPP_IPCP, LCP_CONF_REJ, h->ident,
+ len-4, h+1);
+
+ switch (sp->ipcp.state) {
+ case IPCP_STATE_OPENED:
+ /* Initiate renegotiation. */
+ sppp_ipcp_open (sp);
+ /* fall through... */
+ case IPCP_STATE_ACK_SENT:
+ /* Go to closed state. */
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ }
+ } else {
+ /* Send Configure-Ack packet. */
+ sppp_cp_send (sp, PPP_IPCP, IPCP_CONF_ACK, h->ident,
+ 0, NULL);
+ /* Change the state. */
+ if (sp->ipcp.state == IPCP_STATE_ACK_RCVD)
+ sp->ipcp.state = IPCP_STATE_OPENED;
+ else
+ sp->ipcp.state = IPCP_STATE_ACK_SENT;
+ }
+ break;
+ case IPCP_CONF_ACK:
+ if (h->ident != sp->ipcp.confid)
+ break;
+ sppp_clear_timeout (sp);
+ switch (sp->ipcp.state) {
+ case IPCP_STATE_CLOSED:
+ sp->ipcp.state = IPCP_STATE_ACK_RCVD;
+ sppp_set_timeout (sp, 5);
+ break;
+ case IPCP_STATE_ACK_SENT:
+ sp->ipcp.state = IPCP_STATE_OPENED;
+ break;
+ }
+ break;
+ case IPCP_CONF_NAK:
+ case IPCP_CONF_REJ:
+ if (h->ident != sp->ipcp.confid)
+ break;
+ sppp_clear_timeout (sp);
+ /* Initiate renegotiation. */
+ sppp_ipcp_open (sp);
+ if (sp->ipcp.state != IPCP_STATE_ACK_SENT)
+ /* Go to closed state. */
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ break;
+ case IPCP_TERM_REQ:
+ /* Send Terminate-Ack packet. */
+ sppp_cp_send (sp, PPP_IPCP, IPCP_TERM_ACK, h->ident, 0, NULL);
+ /* Go to closed state. */
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ /* Initiate renegotiation. */
+ sppp_ipcp_open (sp);
+ break;
+ case IPCP_TERM_ACK:
+ /* Ignore for now. */
+ case IPCP_CODE_REJ:
+ /* Ignore for now. */
+ break;
+ }
+}
+
+static void sppp_lcp_open (struct sppp *sp)
+{
+ char opt[6];
+
+ if (! sp->lcp.magic)
+ sp->lcp.magic = jiffies;
+ opt[0] = LCP_OPT_MAGIC;
+ opt[1] = sizeof (opt);
+ opt[2] = sp->lcp.magic >> 24;
+ opt[3] = sp->lcp.magic >> 16;
+ opt[4] = sp->lcp.magic >> 8;
+ opt[5] = sp->lcp.magic;
+ sp->lcp.confid = ++sp->pp_seq;
+ sppp_cp_send (sp, PPP_LCP, LCP_CONF_REQ, sp->lcp.confid,
+ sizeof (opt), &opt);
+ sppp_set_timeout (sp, 2);
+}
+
+static void sppp_ipcp_open (struct sppp *sp)
+{
+ sp->ipcp.confid = ++sp->pp_seq;
+ sppp_cp_send (sp, PPP_IPCP, IPCP_CONF_REQ, sp->ipcp.confid, 0, NULL);
+ sppp_set_timeout (sp, 2);
+}
+
+/*
+ * Process PPP control protocol timeouts.
+ */
+
+static void sppp_cp_timeout (unsigned long arg)
+{
+ struct sppp *sp = (struct sppp*) arg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sp->lock, flags);
+
+ sp->pp_flags &= ~PP_TIMO;
+ if (! (sp->pp_if->flags & IFF_UP) || (sp->pp_flags & PP_CISCO)) {
+ spin_unlock_irqrestore(&sp->lock, flags);
+ return;
+ }
+ switch (sp->lcp.state) {
+ case LCP_STATE_CLOSED:
+ /* No ACK for Configure-Request, retry. */
+ sppp_lcp_open (sp);
+ break;
+ case LCP_STATE_ACK_RCVD:
+ /* ACK got, but no Configure-Request for peer, retry. */
+ sppp_lcp_open (sp);
+ sp->lcp.state = LCP_STATE_CLOSED;
+ break;
+ case LCP_STATE_ACK_SENT:
+ /* ACK sent but no ACK for Configure-Request, retry. */
+ sppp_lcp_open (sp);
+ break;
+ case LCP_STATE_OPENED:
+ /* LCP is already OK, try IPCP. */
+ switch (sp->ipcp.state) {
+ case IPCP_STATE_CLOSED:
+ /* No ACK for Configure-Request, retry. */
+ sppp_ipcp_open (sp);
+ break;
+ case IPCP_STATE_ACK_RCVD:
+ /* ACK got, but no Configure-Request for peer, retry. */
+ sppp_ipcp_open (sp);
+ sp->ipcp.state = IPCP_STATE_CLOSED;
+ break;
+ case IPCP_STATE_ACK_SENT:
+ /* ACK sent but no ACK for Configure-Request, retry. */
+ sppp_ipcp_open (sp);
+ break;
+ case IPCP_STATE_OPENED:
+ /* IPCP is OK. */
+ break;
+ }
+ break;
+ }
+ spin_unlock_irqrestore(&sp->lock, flags);
+ sppp_flush_xmit();
+}
+
+static char *sppp_lcp_type_name (u8 type)
+{
+ static char buf [8];
+ switch (type) {
+ case LCP_CONF_REQ: return ("conf-req");
+ case LCP_CONF_ACK: return ("conf-ack");
+ case LCP_CONF_NAK: return ("conf-nack");
+ case LCP_CONF_REJ: return ("conf-rej");
+ case LCP_TERM_REQ: return ("term-req");
+ case LCP_TERM_ACK: return ("term-ack");
+ case LCP_CODE_REJ: return ("code-rej");
+ case LCP_PROTO_REJ: return ("proto-rej");
+ case LCP_ECHO_REQ: return ("echo-req");
+ case LCP_ECHO_REPLY: return ("echo-reply");
+ case LCP_DISC_REQ: return ("discard-req");
+ }
+ sprintf (buf, "%xh", type);
+ return (buf);
+}
+
+static char *sppp_ipcp_type_name (u8 type)
+{
+ static char buf [8];
+ switch (type) {
+ case IPCP_CONF_REQ: return ("conf-req");
+ case IPCP_CONF_ACK: return ("conf-ack");
+ case IPCP_CONF_NAK: return ("conf-nack");
+ case IPCP_CONF_REJ: return ("conf-rej");
+ case IPCP_TERM_REQ: return ("term-req");
+ case IPCP_TERM_ACK: return ("term-ack");
+ case IPCP_CODE_REJ: return ("code-rej");
+ }
+ sprintf (buf, "%xh", type);
+ return (buf);
+}
+
+static void sppp_print_bytes (u_char *p, u16 len)
+{
+ printk (" %x", *p++);
+ while (--len > 0)
+ printk ("-%x", *p++);
+}
+
+/**
+ * sppp_rcv - receive and process a WAN PPP frame
+ * @skb: The buffer to process
+ * @dev: The device it arrived on
+ * @p: Unused
+ *
+ * Protocol glue. This drives the deferred processing mode the poorer
+ * cards use. This can be called directly by cards that do not have
+ * timing constraints but is normally called from the network layer
+ * after interrupt servicing to process frames queued via netif_rx.
+ */
+
+static int sppp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *p)
+{
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ return NET_RX_DROP;
+ sppp_input(dev,skb);
+ return 0;
+}
+
+struct packet_type sppp_packet_type = {
+ .type = __constant_htons(ETH_P_WAN_PPP),
+ .func = sppp_rcv,
+};
+
+static char banner[] __initdata =
+ KERN_INFO "Cronyx Ltd, Synchronous PPP and CISCO HDLC (c) 1994\n"
+ KERN_INFO "Linux port (c) 1998 Building Number Three Ltd & "
+ "Jan \"Yenya\" Kasprzak.\n";
+
+static int __init sync_ppp_init(void)
+{
+ if(debug)
+ debug=PP_DEBUG;
+ printk(banner);
+ skb_queue_head_init(&tx_queue);
+ dev_add_pack(&sppp_packet_type);
+ return 0;
+}
+
+
+static void __exit sync_ppp_cleanup(void)
+{
+ dev_remove_pack(&sppp_packet_type);
+}
+
+module_init(sync_ppp_init);
+module_exit(sync_ppp_cleanup);
+module_param(debug, int, 0);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/wan/wanpipe_multppp.c b/drivers/net/wan/wanpipe_multppp.c
new file mode 100644
index 00000000000..6aa6987d96c
--- /dev/null
+++ b/drivers/net/wan/wanpipe_multppp.c
@@ -0,0 +1,2357 @@
+/*****************************************************************************
+* wanpipe_multppp.c Multi-Port PPP driver module.
+*
+* Authors: Nenad Corbic <ncorbic@sangoma.com>
+*
+* Copyright: (c) 1995-2001 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Dec 15 2000 Updated for 2.4.X kernel
+* Nov 15 2000 Fixed the SyncPPP support for kernels 2.2.16 and higher.
+* The pppstruct has changed.
+* Jul 13 2000 Using the kernel Syncppp module on top of RAW Wanpipe CHDLC
+* module.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h> /* printk(), and other useful stuff */
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/string.h> /* inline memset(), etc. */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/wanrouter.h> /* WAN router definitions */
+#include <linux/wanpipe.h> /* WANPIPE common user API definitions */
+#include <linux/if_arp.h> /* ARPHRD_* defines */
+
+#include <linux/in.h> /* sockaddr_in */
+#include <linux/inet.h>
+#include <linux/if.h>
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <linux/sdlapci.h>
+#include <asm/io.h>
+
+#include <linux/sdla_chdlc.h> /* CHDLC firmware API definitions */
+#include <linux/sdla_asy.h> /* CHDLC (async) API definitions */
+
+#include <linux/if_wanpipe_common.h> /* Socket Driver common area */
+#include <linux/if_wanpipe.h>
+
+
+#include <linux/inetdevice.h>
+#include <asm/uaccess.h>
+
+#include <net/syncppp.h>
+
+
+/****** Defines & Macros ****************************************************/
+
+#ifdef _DEBUG_
+#define STATIC
+#else
+#define STATIC static
+#endif
+
+/* reasons for enabling the timer interrupt on the adapter */
+#define TMR_INT_ENABLED_UDP 0x01
+#define TMR_INT_ENABLED_UPDATE 0x02
+#define TMR_INT_ENABLED_CONFIG 0x04
+
+#define CHDLC_DFLT_DATA_LEN 1500 /* default MTU */
+#define CHDLC_HDR_LEN 1
+
+#define IFF_POINTTOPOINT 0x10
+
+#define CHDLC_API 0x01
+
+#define PORT(x) (x == 0 ? "PRIMARY" : "SECONDARY" )
+#define MAX_BH_BUFF 10
+
+#define CRC_LENGTH 2
+#define PPP_HEADER_LEN 4
+
+/******Data Structures*****************************************************/
+
+/* This structure is placed in the private data area of the device structure.
+ * The card structure used to occupy the private area but now the following
+ * structure will incorporate the card structure along with CHDLC specific data
+ */
+
+typedef struct chdlc_private_area
+{
+ void *if_ptr; /* General Pointer used by SPPP */
+ wanpipe_common_t common;
+ sdla_t *card;
+ int TracingEnabled; /* For enabling Tracing */
+ unsigned long curr_trace_addr; /* Used for Tracing */
+ unsigned long start_trace_addr;
+ unsigned long end_trace_addr;
+ unsigned long base_addr_trace_buffer;
+ unsigned long end_addr_trace_buffer;
+ unsigned short number_trace_elements;
+ unsigned available_buffer_space;
+ unsigned long router_start_time;
+ unsigned char route_status;
+ unsigned char route_removed;
+ unsigned long tick_counter; /* For 5s timeout counter */
+ unsigned long router_up_time;
+ u32 IP_address; /* IP addressing */
+ u32 IP_netmask;
+ unsigned char mc; /* Mulitcast support on/off */
+ unsigned short udp_pkt_lgth; /* udp packet processing */
+ char udp_pkt_src;
+ char udp_pkt_data[MAX_LGTH_UDP_MGNT_PKT];
+ unsigned short timer_int_enabled;
+ char update_comms_stats; /* updating comms stats */
+
+ //FIXME: add driver stats as per frame relay!
+
+} chdlc_private_area_t;
+
+/* Route Status options */
+#define NO_ROUTE 0x00
+#define ADD_ROUTE 0x01
+#define ROUTE_ADDED 0x02
+#define REMOVE_ROUTE 0x03
+
+
+/* variable for keeping track of enabling/disabling FT1 monitor status */
+static int rCount = 0;
+
+/* variable for tracking how many interfaces to open for WANPIPE on the
+ two ports */
+
+extern void disable_irq(unsigned int);
+extern void enable_irq(unsigned int);
+
+/****** Function Prototypes *************************************************/
+/* WAN link driver entry points. These are called by the WAN router module. */
+static int update(struct wan_device* wandev);
+static int new_if(struct wan_device* wandev, struct net_device* dev,
+ wanif_conf_t* conf);
+static int del_if(struct wan_device* wandev, struct net_device* dev);
+
+/* Network device interface */
+static int if_init(struct net_device* dev);
+static int if_open(struct net_device* dev);
+static int if_close(struct net_device* dev);
+static int if_send(struct sk_buff* skb, struct net_device* dev);
+static struct net_device_stats* if_stats(struct net_device* dev);
+
+static void if_tx_timeout(struct net_device *dev);
+
+/* CHDLC Firmware interface functions */
+static int chdlc_configure (sdla_t* card, void* data);
+static int chdlc_comm_enable (sdla_t* card);
+static int chdlc_comm_disable (sdla_t* card);
+static int chdlc_read_version (sdla_t* card, char* str);
+static int chdlc_set_intr_mode (sdla_t* card, unsigned mode);
+static int chdlc_send (sdla_t* card, void* data, unsigned len);
+static int chdlc_read_comm_err_stats (sdla_t* card);
+static int chdlc_read_op_stats (sdla_t* card);
+static int config_chdlc (sdla_t *card);
+
+
+/* Miscellaneous CHDLC Functions */
+static int set_chdlc_config (sdla_t* card);
+static void init_chdlc_tx_rx_buff(sdla_t* card, struct net_device *dev);
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb);
+static int process_chdlc_exception(sdla_t *card);
+static int process_global_exception(sdla_t *card);
+static int update_comms_stats(sdla_t* card,
+ chdlc_private_area_t* chdlc_priv_area);
+static void port_set_state (sdla_t *card, int);
+
+/* Interrupt handlers */
+static void wsppp_isr (sdla_t* card);
+static void rx_intr (sdla_t* card);
+static void timer_intr(sdla_t *);
+
+/* Miscellaneous functions */
+static int reply_udp( unsigned char *data, unsigned int mbox_len );
+static int intr_test( sdla_t* card);
+static int udp_pkt_type( struct sk_buff *skb , sdla_t* card);
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area);
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area);
+static unsigned short calc_checksum (char *, int);
+static void s508_lock (sdla_t *card, unsigned long *smp_flags);
+static void s508_unlock (sdla_t *card, unsigned long *smp_flags);
+static void send_ppp_term_request(struct net_device *dev);
+
+
+static int Intr_test_counter;
+/****** Public Functions ****************************************************/
+
+/*============================================================================
+ * Cisco HDLC protocol initialization routine.
+ *
+ * This routine is called by the main WANPIPE module during setup. At this
+ * point adapter is completely initialized and firmware is running.
+ * o read firmware version (to make sure it's alive)
+ * o configure adapter
+ * o initialize protocol-specific fields of the adapter data space.
+ *
+ * Return: 0 o.k.
+ * < 0 failure.
+ */
+int wsppp_init (sdla_t* card, wandev_conf_t* conf)
+{
+ unsigned char port_num;
+ int err;
+ unsigned long max_permitted_baud = 0;
+ SHARED_MEMORY_INFO_STRUCT *flags;
+
+ union
+ {
+ char str[80];
+ } u;
+ volatile CHDLC_MAILBOX_STRUCT* mb;
+ CHDLC_MAILBOX_STRUCT* mb1;
+ unsigned long timeout;
+
+ /* Verify configuration ID */
+ if (conf->config_id != WANCONFIG_MPPP) {
+ printk(KERN_INFO "%s: invalid configuration ID %u!\n",
+ card->devname, conf->config_id);
+ return -EINVAL;
+ }
+
+ /* Find out which Port to use */
+ if ((conf->comm_port == WANOPT_PRI) || (conf->comm_port == WANOPT_SEC)){
+ if (card->next){
+
+ if (conf->comm_port != card->next->u.c.comm_port){
+ card->u.c.comm_port = conf->comm_port;
+ }else{
+ printk(KERN_ERR "%s: ERROR - %s port used!\n",
+ card->wandev.name, PORT(conf->comm_port));
+ return -EINVAL;
+ }
+ }else{
+ card->u.c.comm_port = conf->comm_port;
+ }
+ }else{
+ printk(KERN_ERR "%s: ERROR - Invalid Port Selected!\n",
+ card->wandev.name);
+ return -EINVAL;
+ }
+
+
+ /* Initialize protocol-specific fields */
+ if(card->hw.type != SDLA_S514){
+
+ if (card->u.c.comm_port == WANOPT_PRI){
+ card->mbox = (void *) card->hw.dpmbase;
+ }else{
+ card->mbox = (void *) card->hw.dpmbase +
+ SEC_BASE_ADDR_MB_STRUCT - PRI_BASE_ADDR_MB_STRUCT;
+ }
+ }else{
+ /* for a S514 adapter, set a pointer to the actual mailbox in the */
+ /* allocated virtual memory area */
+ if (card->u.c.comm_port == WANOPT_PRI){
+ card->mbox = (void *) card->hw.dpmbase + PRI_BASE_ADDR_MB_STRUCT;
+ }else{
+ card->mbox = (void *) card->hw.dpmbase + SEC_BASE_ADDR_MB_STRUCT;
+ }
+ }
+
+ mb = mb1 = card->mbox;
+
+ if (!card->configured){
+
+ /* The board will place an 'I' in the return code to indicate that it is
+ ready to accept commands. We expect this to be completed in less
+ than 1 second. */
+
+ timeout = jiffies;
+ while (mb->return_code != 'I') /* Wait 1s for board to initialize */
+ if ((jiffies - timeout) > 1*HZ) break;
+
+ if (mb->return_code != 'I') {
+ printk(KERN_INFO
+ "%s: Initialization not completed by adapter\n",
+ card->devname);
+ printk(KERN_INFO "Please contact Sangoma representative.\n");
+ return -EIO;
+ }
+ }
+
+ /* Read firmware version. Note that when adapter initializes, it
+ * clears the mailbox, so it may appear that the first command was
+ * executed successfully when in fact it was merely erased. To work
+ * around this, we execute the first command twice.
+ */
+
+ if (chdlc_read_version(card, u.str))
+ return -EIO;
+
+ printk(KERN_INFO "%s: Running Raw CHDLC firmware v%s\n"
+ "%s: for Multi-Port PPP protocol.\n",
+ card->devname,u.str,card->devname);
+
+ card->isr = &wsppp_isr;
+ card->poll = NULL;
+ card->exec = NULL;
+ card->wandev.update = &update;
+ card->wandev.new_if = &new_if;
+ card->wandev.del_if = &del_if;
+ card->wandev.udp_port = conf->udp_port;
+
+ card->wandev.new_if_cnt = 0;
+
+ /* reset the number of times the 'update()' proc has been called */
+ card->u.c.update_call_count = 0;
+
+ card->wandev.ttl = conf->ttl;
+ card->wandev.interface = conf->interface;
+
+ if ((card->u.c.comm_port == WANOPT_SEC && conf->interface == WANOPT_V35)&&
+ card->hw.type != SDLA_S514){
+ printk(KERN_INFO "%s: ERROR - V35 Interface not supported on S508 %s port \n",
+ card->devname, PORT(card->u.c.comm_port));
+ return -EIO;
+ }
+
+
+ card->wandev.clocking = conf->clocking;
+
+ port_num = card->u.c.comm_port;
+
+ /* Setup Port Bps */
+
+ if(card->wandev.clocking) {
+ if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+ /* For Primary Port 0 */
+ max_permitted_baud =
+ (card->hw.type == SDLA_S514) ?
+ PRI_MAX_BAUD_RATE_S514 :
+ PRI_MAX_BAUD_RATE_S508;
+ }
+ else if(port_num == WANOPT_SEC) {
+ /* For Secondary Port 1 */
+ max_permitted_baud =
+ (card->hw.type == SDLA_S514) ?
+ SEC_MAX_BAUD_RATE_S514 :
+ SEC_MAX_BAUD_RATE_S508;
+ }
+
+ if(conf->bps > max_permitted_baud) {
+ conf->bps = max_permitted_baud;
+ printk(KERN_INFO "%s: Baud too high!\n",
+ card->wandev.name);
+ printk(KERN_INFO "%s: Baud rate set to %lu bps\n",
+ card->wandev.name, max_permitted_baud);
+ }
+
+ card->wandev.bps = conf->bps;
+ }else{
+ card->wandev.bps = 0;
+ }
+
+ /* Setup the Port MTU */
+ if((port_num == WANOPT_PRI) || card->u.c.receive_only) {
+
+ /* For Primary Port 0 */
+ card->wandev.mtu =
+ (conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+ min_t(unsigned int, conf->mtu, PRI_MAX_NO_DATA_BYTES_IN_FRAME) :
+ CHDLC_DFLT_DATA_LEN;
+ } else if(port_num == WANOPT_SEC) {
+ /* For Secondary Port 1 */
+ card->wandev.mtu =
+ (conf->mtu >= MIN_LGTH_CHDLC_DATA_CFG) ?
+ min_t(unsigned int, conf->mtu, SEC_MAX_NO_DATA_BYTES_IN_FRAME) :
+ CHDLC_DFLT_DATA_LEN;
+ }
+
+ /* Add on a PPP Header */
+ card->wandev.mtu += PPP_HEADER_LEN;
+
+ /* Set up the interrupt status area */
+ /* Read the CHDLC Configuration and obtain:
+ * Ptr to shared memory infor struct
+ * Use this pointer to calculate the value of card->u.c.flags !
+ */
+ mb1->buffer_length = 0;
+ mb1->command = READ_CHDLC_CONFIGURATION;
+ err = sdla_exec(mb1) ? mb1->return_code : CMD_TIMEOUT;
+ if(err != COMMAND_OK) {
+ clear_bit(1, (void*)&card->wandev.critical);
+
+ if(card->hw.type != SDLA_S514)
+ enable_irq(card->hw.irq);
+
+ chdlc_error(card, err, mb1);
+ return -EIO;
+ }
+
+ if(card->hw.type == SDLA_S514){
+ card->u.c.flags = (void *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+ ptr_shared_mem_info_struct));
+ }else{
+ card->u.c.flags = (void *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb1->data)->
+ ptr_shared_mem_info_struct % SDLA_WINDOWSIZE));
+ }
+
+ flags = card->u.c.flags;
+
+ /* This is for the ports link state */
+ card->wandev.state = WAN_DUALPORT;
+ card->u.c.state = WAN_DISCONNECTED;
+
+
+ if (!card->wandev.piggyback){
+ err = intr_test(card);
+
+ if(err || (Intr_test_counter < MAX_INTR_TEST_COUNTER)) {
+ printk(KERN_ERR "%s: Interrupt test failed (%i)\n",
+ card->devname, Intr_test_counter);
+ printk(KERN_ERR "%s: Please choose another interrupt\n",
+ card->devname);
+ return -EIO;
+ }
+
+ printk(KERN_INFO "%s: Interrupt test passed (%i)\n",
+ card->devname, Intr_test_counter);
+ }
+
+
+ if (chdlc_set_intr_mode(card, APP_INT_ON_TIMER)){
+ printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+ card->devname);
+ return -EIO;
+ }
+
+ /* Mask the Timer interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TIMER;
+
+ printk(KERN_INFO "\n");
+
+ return 0;
+}
+
+/******* WAN Device Driver Entry Points *************************************/
+
+/*============================================================================
+ * Update device status & statistics
+ * This procedure is called when updating the PROC file system and returns
+ * various communications statistics. These statistics are accumulated from 3
+ * different locations:
+ * 1) The 'if_stats' recorded for the device.
+ * 2) Communication error statistics on the adapter.
+ * 3) CHDLC operational statistics on the adapter.
+ * The board level statistics are read during a timer interrupt. Note that we
+ * read the error and operational statistics during consecitive timer ticks so
+ * as to minimize the time that we are inside the interrupt handler.
+ *
+ */
+static int update(struct wan_device* wandev)
+{
+ sdla_t* card = wandev->private;
+ struct net_device* dev;
+ volatile chdlc_private_area_t* chdlc_priv_area;
+ SHARED_MEMORY_INFO_STRUCT *flags;
+ unsigned long timeout;
+
+ /* sanity checks */
+ if((wandev == NULL) || (wandev->private == NULL))
+ return -EFAULT;
+
+ if(wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ /* more sanity checks */
+ if(!card->u.c.flags)
+ return -ENODEV;
+
+ if((dev=card->wandev.dev) == NULL)
+ return -ENODEV;
+
+ if((chdlc_priv_area=dev->priv) == NULL)
+ return -ENODEV;
+
+ flags = card->u.c.flags;
+
+ if(chdlc_priv_area->update_comms_stats){
+ return -EAGAIN;
+ }
+
+ /* we will need 2 timer interrupts to complete the */
+ /* reading of the statistics */
+ chdlc_priv_area->update_comms_stats = 2;
+ flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+ chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UPDATE;
+
+ /* wait a maximum of 1 second for the statistics to be updated */
+ timeout = jiffies;
+ for(;;) {
+ if(chdlc_priv_area->update_comms_stats == 0)
+ break;
+ if ((jiffies - timeout) > (1 * HZ)){
+ chdlc_priv_area->update_comms_stats = 0;
+ chdlc_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_UPDATE;
+ return -EAGAIN;
+ }
+ }
+
+ return 0;
+}
+
+
+/*============================================================================
+ * Create new logical channel.
+ * This routine is called by the router when ROUTER_IFNEW IOCTL is being
+ * handled.
+ * o parse media- and hardware-specific configuration
+ * o make sure that a new channel can be created
+ * o allocate resources, if necessary
+ * o prepare network device structure for registaration.
+ *
+ * Return: 0 o.k.
+ * < 0 failure (channel will not be created)
+ */
+static int new_if(struct wan_device* wandev, struct net_device* pdev,
+ wanif_conf_t* conf)
+{
+
+ struct ppp_device *pppdev = (struct ppp_device *)pdev;
+ struct net_device *dev = NULL;
+ struct sppp *sp;
+ sdla_t* card = wandev->private;
+ chdlc_private_area_t* chdlc_priv_area;
+
+ if ((conf->name[0] == '\0') || (strlen(conf->name) > WAN_IFNAME_SZ)) {
+ printk(KERN_INFO "%s: invalid interface name!\n",
+ card->devname);
+ return -EINVAL;
+ }
+
+ /* allocate and initialize private data */
+ chdlc_priv_area = kmalloc(sizeof(chdlc_private_area_t), GFP_KERNEL);
+
+ if(chdlc_priv_area == NULL)
+ return -ENOMEM;
+
+ memset(chdlc_priv_area, 0, sizeof(chdlc_private_area_t));
+
+ chdlc_priv_area->card = card;
+
+ /* initialize data */
+ strcpy(card->u.c.if_name, conf->name);
+
+ if(card->wandev.new_if_cnt > 0) {
+ kfree(chdlc_priv_area);
+ return -EEXIST;
+ }
+
+ card->wandev.new_if_cnt++;
+
+ chdlc_priv_area->TracingEnabled = 0;
+
+ //We don't need this any more
+ chdlc_priv_area->route_status = NO_ROUTE;
+ chdlc_priv_area->route_removed = 0;
+
+ printk(KERN_INFO "%s: Firmware running in HDLC STREAMING Mode\n",
+ wandev->name);
+
+ /* Setup wanpipe as a router (WANPIPE) or as an API */
+ if( strcmp(conf->usedby, "WANPIPE") == 0) {
+ printk(KERN_INFO "%s: Driver running in WANPIPE mode!\n",
+ wandev->name);
+ card->u.c.usedby = WANPIPE;
+ } else {
+ printk(KERN_INFO
+ "%s: API Mode is not supported for SyncPPP!\n",
+ wandev->name);
+ kfree(chdlc_priv_area);
+ return -EINVAL;
+ }
+
+ /* Get Multicast Information */
+ chdlc_priv_area->mc = conf->mc;
+
+
+ chdlc_priv_area->if_ptr = pppdev;
+
+ /* prepare network device data space for registration */
+
+ strcpy(dev->name,card->u.c.if_name);
+
+ /* Attach PPP protocol layer to pppdev
+ * The sppp_attach() will initilize the dev structure
+ * and setup ppp layer protocols.
+ * All we have to do is to bind in:
+ * if_open(), if_close(), if_send() and get_stats() functions.
+ */
+ sppp_attach(pppdev);
+ dev = pppdev->dev;
+ sp = &pppdev->sppp;
+
+ /* Enable PPP Debugging */
+ // FIXME Fix this up somehow
+ //sp->pp_flags |= PP_DEBUG;
+ sp->pp_flags &= ~PP_CISCO;
+
+ dev->init = &if_init;
+ dev->priv = chdlc_priv_area;
+
+ return 0;
+}
+
+
+
+
+/*============================================================================
+ * Delete logical channel.
+ */
+static int del_if(struct wan_device* wandev, struct net_device* dev)
+{
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+ sdla_t *card = chdlc_priv_area->card;
+ unsigned long smp_lock;
+
+ /* Detach the PPP layer */
+ printk(KERN_INFO "%s: Detaching SyncPPP Module from %s\n",
+ wandev->name,dev->name);
+
+ lock_adapter_irq(&wandev->lock,&smp_lock);
+
+ sppp_detach(dev);
+ chdlc_priv_area->if_ptr=NULL;
+
+ chdlc_set_intr_mode(card, 0);
+ if (card->u.c.comm_enabled)
+ chdlc_comm_disable(card);
+ unlock_adapter_irq(&wandev->lock,&smp_lock);
+
+ port_set_state(card, WAN_DISCONNECTED);
+
+ return 0;
+}
+
+
+/****** Network Device Interface ********************************************/
+
+/*============================================================================
+ * Initialize Linux network interface.
+ *
+ * This routine is called only once for each interface, during Linux network
+ * interface registration. Returning anything but zero will fail interface
+ * registration.
+ */
+static int if_init(struct net_device* dev)
+{
+ chdlc_private_area_t* chdlc_priv_area = dev->priv;
+ sdla_t* card = chdlc_priv_area->card;
+ struct wan_device* wandev = &card->wandev;
+
+ /* NOTE: Most of the dev initialization was
+ * done in sppp_attach(), called by new_if()
+ * function. All we have to do here is
+ * to link four major routines below.
+ */
+
+ /* Initialize device driver entry points */
+ dev->open = &if_open;
+ dev->stop = &if_close;
+ dev->hard_start_xmit = &if_send;
+ dev->get_stats = &if_stats;
+ dev->tx_timeout = &if_tx_timeout;
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+
+ /* Initialize hardware parameters */
+ dev->irq = wandev->irq;
+ dev->dma = wandev->dma;
+ dev->base_addr = wandev->ioport;
+ dev->mem_start = wandev->maddr;
+ dev->mem_end = wandev->maddr + wandev->msize - 1;
+
+ /* Set transmit buffer queue length
+ * If we over fill this queue the packets will
+ * be droped by the kernel.
+ * sppp_attach() sets this to 10, but
+ * 100 will give us more room at low speeds.
+ */
+ dev->tx_queue_len = 100;
+
+ return 0;
+}
+
+
+/*============================================================================
+ * Handle transmit timeout event from netif watchdog
+ */
+static void if_tx_timeout(struct net_device *dev)
+{
+ chdlc_private_area_t* chan = dev->priv;
+ sdla_t *card = chan->card;
+
+ /* If our device stays busy for at least 5 seconds then we will
+ * kick start the device by making dev->tbusy = 0. We expect
+ * that our device never stays busy more than 5 seconds. So this
+ * is only used as a last resort.
+ */
+
+ ++card->wandev.stats.collisions;
+
+ printk (KERN_INFO "%s: Transmit timed out on %s\n", card->devname,dev->name);
+ netif_wake_queue (dev);
+}
+
+
+/*============================================================================
+ * Open network interface.
+ * o enable communications and interrupts.
+ * o prevent module from unloading by incrementing use count
+ *
+ * Return 0 if O.k. or errno.
+ */
+static int if_open(struct net_device* dev)
+{
+ chdlc_private_area_t* chdlc_priv_area = dev->priv;
+ sdla_t* card = chdlc_priv_area->card;
+ struct timeval tv;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+ /* Only one open per interface is allowed */
+ if (netif_running(dev))
+ return -EBUSY;
+
+ /* Start PPP Layer */
+ if (sppp_open(dev)){
+ return -EIO;
+ }
+
+ do_gettimeofday(&tv);
+ chdlc_priv_area->router_start_time = tv.tv_sec;
+
+ netif_start_queue(dev);
+
+ wanpipe_open(card);
+
+ chdlc_priv_area->timer_int_enabled |= TMR_INT_ENABLED_CONFIG;
+ flags->interrupt_info_struct.interrupt_permission |= APP_INT_ON_TIMER;
+ return 0;
+}
+
+/*============================================================================
+ * Close network interface.
+ * o if this is the last close, then disable communications and interrupts.
+ * o reset flags.
+ */
+static int if_close(struct net_device* dev)
+{
+ chdlc_private_area_t* chdlc_priv_area = dev->priv;
+ sdla_t* card = chdlc_priv_area->card;
+
+ /* Stop the PPP Layer */
+ sppp_close(dev);
+ netif_stop_queue(dev);
+
+ wanpipe_close(card);
+
+ return 0;
+}
+
+/*============================================================================
+ * Send a packet on a network interface.
+ * o set tbusy flag (marks start of the transmission) to block a timer-based
+ * transmit from overlapping.
+ * o check link state. If link is not up, then drop the packet.
+ * o execute adapter send command.
+ * o free socket buffer
+ *
+ * Return: 0 complete (socket buffer must be freed)
+ * non-0 packet may be re-transmitted (tbusy must be set)
+ *
+ * Notes:
+ * 1. This routine is called either by the protocol stack or by the "net
+ * bottom half" (with interrupts enabled).
+ * 2. Setting tbusy flag will inhibit further transmit requests from the
+ * protocol stack and can be used for flow control with protocol layer.
+ */
+static int if_send(struct sk_buff* skb, struct net_device* dev)
+{
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+ sdla_t *card = chdlc_priv_area->card;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ INTERRUPT_INFORMATION_STRUCT *chdlc_int = &flags->interrupt_info_struct;
+ int udp_type = 0;
+ unsigned long smp_flags;
+ int err=0;
+
+ netif_stop_queue(dev);
+
+
+ if (skb == NULL){
+ /* If we get here, some higher layer thinks we've missed an
+ * tx-done interrupt.
+ */
+ printk(KERN_INFO "%s: Received NULL skb buffer! interface %s got kicked!\n",
+ card->devname, dev->name);
+
+ netif_wake_queue(dev);
+ return 0;
+ }
+
+ if (ntohs(skb->protocol) != htons(PVC_PROT)){
+ /* check the udp packet type */
+
+ udp_type = udp_pkt_type(skb, card);
+ if (udp_type == UDP_CPIPE_TYPE){
+ if(store_udp_mgmt_pkt(UDP_PKT_FRM_STACK, card, skb, dev,
+ chdlc_priv_area)){
+ chdlc_int->interrupt_permission |=
+ APP_INT_ON_TIMER;
+ }
+ netif_start_queue(dev);
+ return 0;
+ }
+ }
+
+ /* Lock the 508 Card: SMP is supported */
+ if(card->hw.type != SDLA_S514){
+ s508_lock(card,&smp_flags);
+ }
+
+ if (test_and_set_bit(SEND_CRIT, (void*)&card->wandev.critical)){
+
+ printk(KERN_INFO "%s: Critical in if_send: %lx\n",
+ card->wandev.name,card->wandev.critical);
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+ goto if_send_crit_exit;
+ }
+
+ if (card->wandev.state != WAN_CONNECTED){
+ ++card->wandev.stats.tx_dropped;
+ netif_start_queue(dev);
+ goto if_send_crit_exit;
+ }
+
+ if (chdlc_send(card, skb->data, skb->len)){
+ netif_stop_queue(dev);
+
+ }else{
+ ++card->wandev.stats.tx_packets;
+ card->wandev.stats.tx_bytes += skb->len;
+ dev->trans_start = jiffies;
+ netif_start_queue(dev);
+ }
+
+if_send_crit_exit:
+ if (!(err=netif_queue_stopped(dev))){
+ dev_kfree_skb_any(skb);
+ }else{
+ chdlc_priv_area->tick_counter = jiffies;
+ chdlc_int->interrupt_permission |= APP_INT_ON_TX_FRAME;
+ }
+
+ clear_bit(SEND_CRIT, (void*)&card->wandev.critical);
+ if(card->hw.type != SDLA_S514){
+ s508_unlock(card,&smp_flags);
+ }
+
+ return err;
+}
+
+
+/*============================================================================
+ * Reply to UDP Management system.
+ * Return length of reply.
+ */
+static int reply_udp( unsigned char *data, unsigned int mbox_len )
+{
+
+ unsigned short len, udp_length, temp, ip_length;
+ unsigned long ip_temp;
+ int even_bound = 0;
+ chdlc_udp_pkt_t *c_udp_pkt = (chdlc_udp_pkt_t *)data;
+
+ /* Set length of packet */
+ len = sizeof(ip_pkt_t)+
+ sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ sizeof(trace_info_t)+
+ mbox_len;
+
+ /* fill in UDP reply */
+ c_udp_pkt->wp_mgmt.request_reply = UDPMGMT_REPLY;
+
+ /* fill in UDP length */
+ udp_length = sizeof(udp_pkt_t)+
+ sizeof(wp_mgmt_t)+
+ sizeof(cblock_t)+
+ sizeof(trace_info_t)+
+ mbox_len;
+
+ /* put it on an even boundary */
+ if ( udp_length & 0x0001 ) {
+ udp_length += 1;
+ len += 1;
+ even_bound = 1;
+ }
+
+ temp = (udp_length<<8)|(udp_length>>8);
+ c_udp_pkt->udp_pkt.udp_length = temp;
+
+ /* swap UDP ports */
+ temp = c_udp_pkt->udp_pkt.udp_src_port;
+ c_udp_pkt->udp_pkt.udp_src_port =
+ c_udp_pkt->udp_pkt.udp_dst_port;
+ c_udp_pkt->udp_pkt.udp_dst_port = temp;
+
+ /* add UDP pseudo header */
+ temp = 0x1100;
+ *((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound)) = temp;
+ temp = (udp_length<<8)|(udp_length>>8);
+ *((unsigned short *)(c_udp_pkt->data+mbox_len+even_bound+2)) = temp;
+
+
+ /* calculate UDP checksum */
+ c_udp_pkt->udp_pkt.udp_checksum = 0;
+ c_udp_pkt->udp_pkt.udp_checksum = calc_checksum(&data[UDP_OFFSET],udp_length+UDP_OFFSET);
+
+ /* fill in IP length */
+ ip_length = len;
+ temp = (ip_length<<8)|(ip_length>>8);
+ c_udp_pkt->ip_pkt.total_length = temp;
+
+ /* swap IP addresses */
+ ip_temp = c_udp_pkt->ip_pkt.ip_src_address;
+ c_udp_pkt->ip_pkt.ip_src_address = c_udp_pkt->ip_pkt.ip_dst_address;
+ c_udp_pkt->ip_pkt.ip_dst_address = ip_temp;
+
+ /* fill in IP checksum */
+ c_udp_pkt->ip_pkt.hdr_checksum = 0;
+ c_udp_pkt->ip_pkt.hdr_checksum = calc_checksum(data,sizeof(ip_pkt_t));
+
+ return len;
+
+} /* reply_udp */
+
+unsigned short calc_checksum (char *data, int len)
+{
+ unsigned short temp;
+ unsigned long sum=0;
+ int i;
+
+ for( i = 0; i <len; i+=2 ) {
+ memcpy(&temp,&data[i],2);
+ sum += (unsigned long)temp;
+ }
+
+ while (sum >> 16 ) {
+ sum = (sum & 0xffffUL) + (sum >> 16);
+ }
+
+ temp = (unsigned short)sum;
+ temp = ~temp;
+
+ if( temp == 0 )
+ temp = 0xffff;
+
+ return temp;
+}
+
+
+/*============================================================================
+ * Get ethernet-style interface statistics.
+ * Return a pointer to struct enet_statistics.
+ */
+static struct net_device_stats* if_stats(struct net_device* dev)
+{
+ sdla_t *my_card;
+ chdlc_private_area_t* chdlc_priv_area;
+
+ /* Shutdown bug fix. In del_if() we kill
+ * dev->priv pointer. This function, gets
+ * called after del_if(), thus check
+ * if pointer has been deleted */
+ if ((chdlc_priv_area=dev->priv) == NULL)
+ return NULL;
+
+ my_card = chdlc_priv_area->card;
+ return &my_card->wandev.stats;
+}
+
+
+/****** Cisco HDLC Firmware Interface Functions *******************************/
+
+/*============================================================================
+ * Read firmware code version.
+ * Put code version as ASCII string in str.
+ */
+static int chdlc_read_version (sdla_t* card, char* str)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int len;
+ char err;
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if(err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ }
+ else if (str) { /* is not null */
+ len = mb->buffer_length;
+ memcpy(str, mb->data, len);
+ str[len] = '\0';
+ }
+ return (err);
+}
+
+/*-----------------------------------------------------------------------------
+ * Configure CHDLC firmware.
+ */
+static int chdlc_configure (sdla_t* card, void* data)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT *mailbox = card->mbox;
+ int data_length = sizeof(CHDLC_CONFIGURATION_STRUCT);
+
+ mailbox->buffer_length = data_length;
+ memcpy(mailbox->data, data, data_length);
+ mailbox->command = SET_CHDLC_CONFIGURATION;
+ err = sdla_exec(mailbox) ? mailbox->return_code : CMD_TIMEOUT;
+
+ if (err != COMMAND_OK) chdlc_error (card, err, mailbox);
+
+ return err;
+}
+
+
+/*============================================================================
+ * Set interrupt mode -- HDLC Version.
+ */
+
+static int chdlc_set_intr_mode (sdla_t* card, unsigned mode)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ CHDLC_INT_TRIGGERS_STRUCT* int_data =
+ (CHDLC_INT_TRIGGERS_STRUCT *)mb->data;
+ int err;
+
+ int_data->CHDLC_interrupt_triggers = mode;
+ int_data->IRQ = card->hw.irq;
+ int_data->interrupt_timer = 1;
+
+ mb->buffer_length = sizeof(CHDLC_INT_TRIGGERS_STRUCT);
+ mb->command = SET_CHDLC_INTERRUPT_TRIGGERS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error (card, err, mb);
+ return err;
+}
+
+
+/*============================================================================
+ * Enable communications.
+ */
+
+static int chdlc_comm_enable (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = ENABLE_CHDLC_COMMUNICATIONS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card, err, mb);
+ else
+ card->u.c.comm_enabled=1;
+
+ return err;
+}
+
+/*============================================================================
+ * Disable communications and Drop the Modem lines (DCD and RTS).
+ */
+static int chdlc_comm_disable (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = DISABLE_CHDLC_COMMUNICATIONS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card,err,mb);
+
+ return err;
+}
+
+/*============================================================================
+ * Read communication error statistics.
+ */
+static int chdlc_read_comm_err_stats (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = READ_COMMS_ERROR_STATS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card,err,mb);
+ return err;
+}
+
+
+/*============================================================================
+ * Read CHDLC operational statistics.
+ */
+static int chdlc_read_op_stats (sdla_t* card)
+{
+ int err;
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_OPERATIONAL_STATS;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK)
+ chdlc_error(card,err,mb);
+ return err;
+}
+
+
+/*============================================================================
+ * Update communications error and general packet statistics.
+ */
+static int update_comms_stats(sdla_t* card,
+ chdlc_private_area_t* chdlc_priv_area)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ COMMS_ERROR_STATS_STRUCT* err_stats;
+ CHDLC_OPERATIONAL_STATS_STRUCT *op_stats;
+
+ /* on the first timer interrupt, read the comms error statistics */
+ if(chdlc_priv_area->update_comms_stats == 2) {
+ if(chdlc_read_comm_err_stats(card))
+ return 1;
+ err_stats = (COMMS_ERROR_STATS_STRUCT *)mb->data;
+ card->wandev.stats.rx_over_errors =
+ err_stats->Rx_overrun_err_count;
+ card->wandev.stats.rx_crc_errors =
+ err_stats->CRC_err_count;
+ card->wandev.stats.rx_frame_errors =
+ err_stats->Rx_abort_count;
+ card->wandev.stats.rx_fifo_errors =
+ err_stats->Rx_dis_pri_bfrs_full_count;
+ card->wandev.stats.rx_missed_errors =
+ card->wandev.stats.rx_fifo_errors;
+ card->wandev.stats.tx_aborted_errors =
+ err_stats->sec_Tx_abort_count;
+ }
+
+ /* on the second timer interrupt, read the operational statistics */
+ else {
+ if(chdlc_read_op_stats(card))
+ return 1;
+ op_stats = (CHDLC_OPERATIONAL_STATS_STRUCT *)mb->data;
+ card->wandev.stats.rx_length_errors =
+ (op_stats->Rx_Data_discard_short_count +
+ op_stats->Rx_Data_discard_long_count);
+ }
+
+ return 0;
+}
+
+/*============================================================================
+ * Send packet.
+ * Return: 0 - o.k.
+ * 1 - no transmit buffers available
+ */
+static int chdlc_send (sdla_t* card, void* data, unsigned len)
+{
+ CHDLC_DATA_TX_STATUS_EL_STRUCT *txbuf = card->u.c.txbuf;
+
+ if (txbuf->opp_flag)
+ return 1;
+
+ sdla_poke(&card->hw, txbuf->ptr_data_bfr, data, len);
+
+ txbuf->frame_length = len;
+ txbuf->opp_flag = 1; /* start transmission */
+
+ /* Update transmit buffer control fields */
+ card->u.c.txbuf = ++txbuf;
+
+ if ((void*)txbuf > card->u.c.txbuf_last)
+ card->u.c.txbuf = card->u.c.txbuf_base;
+
+ return 0;
+}
+
+/****** Firmware Error Handler **********************************************/
+
+/*============================================================================
+ * Firmware error handler.
+ * This routine is called whenever firmware command returns non-zero
+ * return code.
+ *
+ * Return zero if previous command has to be cancelled.
+ */
+static int chdlc_error (sdla_t *card, int err, CHDLC_MAILBOX_STRUCT *mb)
+{
+ unsigned cmd = mb->command;
+
+ switch (err) {
+
+ case CMD_TIMEOUT:
+ printk(KERN_ERR "%s: command 0x%02X timed out!\n",
+ card->devname, cmd);
+ break;
+
+ case S514_BOTH_PORTS_SAME_CLK_MODE:
+ if(cmd == SET_CHDLC_CONFIGURATION) {
+ printk(KERN_INFO
+ "%s: Configure both ports for the same clock source\n",
+ card->devname);
+ break;
+ }
+
+ default:
+ printk(KERN_INFO "%s: command 0x%02X returned 0x%02X!\n",
+ card->devname, cmd, err);
+ }
+
+ return 0;
+}
+
+/****** Interrupt Handlers **************************************************/
+
+/*============================================================================
+ * Cisco HDLC interrupt service routine.
+ */
+STATIC void wsppp_isr (sdla_t* card)
+{
+ struct net_device* dev;
+ SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+ int i;
+ sdla_t *my_card;
+
+
+ /* Check for which port the interrupt has been generated
+ * Since Secondary Port is piggybacking on the Primary
+ * the check must be done here.
+ */
+
+ flags = card->u.c.flags;
+ if (!flags->interrupt_info_struct.interrupt_type){
+ /* Check for a second port (piggybacking) */
+ if((my_card = card->next)){
+ flags = my_card->u.c.flags;
+ if (flags->interrupt_info_struct.interrupt_type){
+ card = my_card;
+ card->isr(card);
+ return;
+ }
+ }
+ }
+
+ dev = card->wandev.dev;
+ card->in_isr = 1;
+ flags = card->u.c.flags;
+
+ /* If we get an interrupt with no network device, stop the interrupts
+ * and issue an error */
+ if ((!dev || !dev->priv) && flags->interrupt_info_struct.interrupt_type !=
+ COMMAND_COMPLETE_APP_INT_PEND){
+ goto isr_done;
+ }
+
+
+ /* if critical due to peripheral operations
+ * ie. update() or getstats() then reset the interrupt and
+ * wait for the board to retrigger.
+ */
+ if(test_bit(PERI_CRIT, (void*)&card->wandev.critical)) {
+ flags->interrupt_info_struct.
+ interrupt_type = 0;
+ goto isr_done;
+ }
+
+
+ /* On a 508 Card, if critical due to if_send
+ * Major Error !!!
+ */
+ if(card->hw.type != SDLA_S514) {
+ if(test_bit(0, (void*)&card->wandev.critical)) {
+ printk(KERN_INFO "%s: Critical while in ISR: %lx\n",
+ card->devname, card->wandev.critical);
+ goto isr_done;
+ }
+ }
+
+ switch(flags->interrupt_info_struct.interrupt_type) {
+
+ case RX_APP_INT_PEND: /* 0x01: receive interrupt */
+ rx_intr(card);
+ break;
+
+ case TX_APP_INT_PEND: /* 0x02: transmit interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TX_FRAME;
+
+ netif_wake_queue(dev);
+ break;
+
+ case COMMAND_COMPLETE_APP_INT_PEND:/* 0x04: cmd cplt */
+ ++ Intr_test_counter;
+ break;
+
+ case CHDLC_EXCEP_COND_APP_INT_PEND: /* 0x20 */
+ process_chdlc_exception(card);
+ break;
+
+ case GLOBAL_EXCEP_COND_APP_INT_PEND:
+ process_global_exception(card);
+ break;
+
+ case TIMER_APP_INT_PEND:
+ timer_intr(card);
+ break;
+
+ default:
+ printk(KERN_INFO "%s: spurious interrupt 0x%02X!\n",
+ card->devname,
+ flags->interrupt_info_struct.interrupt_type);
+ printk(KERN_INFO "Code name: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codename[i]);
+ printk(KERN_INFO "\nCode version: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codeversion[i]);
+ printk(KERN_INFO "\n");
+ break;
+ }
+
+isr_done:
+ card->in_isr = 0;
+ flags->interrupt_info_struct.interrupt_type = 0;
+}
+
+/*============================================================================
+ * Receive interrupt handler.
+ */
+static void rx_intr (sdla_t* card)
+{
+ struct net_device *dev;
+ chdlc_private_area_t *chdlc_priv_area;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ CHDLC_DATA_RX_STATUS_EL_STRUCT *rxbuf = card->u.c.rxmb;
+ struct sk_buff *skb;
+ unsigned len;
+ unsigned addr = rxbuf->ptr_data_bfr;
+ void *buf;
+ int i,udp_type;
+
+ if (rxbuf->opp_flag != 0x01) {
+ printk(KERN_INFO
+ "%s: corrupted Rx buffer @ 0x%X, flag = 0x%02X!\n",
+ card->devname, (unsigned)rxbuf, rxbuf->opp_flag);
+ printk(KERN_INFO "Code name: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codename[i]);
+ printk(KERN_INFO "\nCode version: ");
+ for(i = 0; i < 4; i ++)
+ printk(KERN_INFO "%c",
+ flags->global_info_struct.codeversion[i]);
+ printk(KERN_INFO "\n");
+
+
+ /* Bug Fix: Mar 6 2000
+ * If we get a corrupted mailbox, it measn that driver
+ * is out of sync with the firmware. There is no recovery.
+ * If we don't turn off all interrupts for this card
+ * the machine will crash.
+ */
+ printk(KERN_INFO "%s: Critical router failure ...!!!\n", card->devname);
+ printk(KERN_INFO "Please contact Sangoma Technologies !\n");
+ chdlc_set_intr_mode(card,0);
+ return;
+ }
+
+ dev = card->wandev.dev;
+
+ if (!dev){
+ goto rx_exit;
+ }
+
+ if (!netif_running(dev)){
+ goto rx_exit;
+ }
+
+ chdlc_priv_area = dev->priv;
+
+ if (rxbuf->error_flag){
+ goto rx_exit;
+ }
+ /* Take off two CRC bytes */
+
+ if (rxbuf->frame_length < 7 || rxbuf->frame_length > 1506 ){
+ goto rx_exit;
+ }
+
+ len = rxbuf->frame_length - CRC_LENGTH;
+
+ /* Allocate socket buffer */
+ skb = dev_alloc_skb(len);
+
+ if (skb == NULL) {
+ if (net_ratelimit()){
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ }
+ ++card->wandev.stats.rx_dropped;
+ goto rx_exit;
+ }
+
+ /* Copy data to the socket buffer */
+ if((addr + len) > card->u.c.rx_top + 1) {
+ unsigned tmp = card->u.c.rx_top - addr + 1;
+ buf = skb_put(skb, tmp);
+ sdla_peek(&card->hw, addr, buf, tmp);
+ addr = card->u.c.rx_base;
+ len -= tmp;
+ }
+
+ buf = skb_put(skb, len);
+ sdla_peek(&card->hw, addr, buf, len);
+
+ skb->protocol = htons(ETH_P_WAN_PPP);
+
+ card->wandev.stats.rx_packets ++;
+ card->wandev.stats.rx_bytes += skb->len;
+ udp_type = udp_pkt_type( skb, card );
+
+ if(udp_type == UDP_CPIPE_TYPE) {
+ if(store_udp_mgmt_pkt(UDP_PKT_FRM_NETWORK,
+ card, skb, dev, chdlc_priv_area)) {
+ flags->interrupt_info_struct.
+ interrupt_permission |=
+ APP_INT_ON_TIMER;
+ }
+ }else{
+ /* Pass it up the protocol stack */
+ skb->dev = dev;
+ skb->mac.raw = skb->data;
+ netif_rx(skb);
+ dev->last_rx = jiffies;
+ }
+
+rx_exit:
+ /* Release buffer element and calculate a pointer to the next one */
+ rxbuf->opp_flag = 0x00;
+ card->u.c.rxmb = ++ rxbuf;
+ if((void*)rxbuf > card->u.c.rxbuf_last){
+ card->u.c.rxmb = card->u.c.rxbuf_base;
+ }
+}
+
+/*============================================================================
+ * Timer interrupt handler.
+ * The timer interrupt is used for two purposes:
+ * 1) Processing udp calls from 'cpipemon'.
+ * 2) Reading board-level statistics for updating the proc file system.
+ */
+void timer_intr(sdla_t *card)
+{
+ struct net_device* dev;
+ chdlc_private_area_t* chdlc_priv_area = NULL;
+ SHARED_MEMORY_INFO_STRUCT* flags = NULL;
+
+ dev = card->wandev.dev;
+ chdlc_priv_area = dev->priv;
+
+ if (chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_CONFIG) {
+ if (!config_chdlc(card)){
+ chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_CONFIG;
+ }
+ }
+
+ /* process a udp call if pending */
+ if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UDP) {
+ process_udp_mgmt_pkt(card, dev,
+ chdlc_priv_area);
+ chdlc_priv_area->timer_int_enabled &= ~TMR_INT_ENABLED_UDP;
+ }
+
+
+ /* read the communications statistics if required */
+ if(chdlc_priv_area->timer_int_enabled & TMR_INT_ENABLED_UPDATE) {
+ update_comms_stats(card, chdlc_priv_area);
+ if(!(-- chdlc_priv_area->update_comms_stats)) {
+ chdlc_priv_area->timer_int_enabled &=
+ ~TMR_INT_ENABLED_UPDATE;
+ }
+ }
+
+ /* only disable the timer interrupt if there are no udp or statistic */
+ /* updates pending */
+ if(!chdlc_priv_area->timer_int_enabled) {
+ flags = card->u.c.flags;
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~APP_INT_ON_TIMER;
+ }
+}
+
+/*------------------------------------------------------------------------------
+ Miscellaneous Functions
+ - set_chdlc_config() used to set configuration options on the board
+------------------------------------------------------------------------------*/
+
+static int set_chdlc_config(sdla_t* card)
+{
+
+ CHDLC_CONFIGURATION_STRUCT cfg;
+
+ memset(&cfg, 0, sizeof(CHDLC_CONFIGURATION_STRUCT));
+
+ if(card->wandev.clocking)
+ cfg.baud_rate = card->wandev.bps;
+
+ cfg.line_config_options = (card->wandev.interface == WANOPT_RS232) ?
+ INTERFACE_LEVEL_RS232 : INTERFACE_LEVEL_V35;
+
+ cfg.modem_config_options = 0;
+ //API OPTIONS
+ cfg.CHDLC_API_options = DISCARD_RX_ERROR_FRAMES;
+ cfg.modem_status_timer = 100;
+ cfg.CHDLC_protocol_options = HDLC_STREAMING_MODE;
+ cfg.percent_data_buffer_for_Tx = 50;
+ cfg.CHDLC_statistics_options = (CHDLC_TX_DATA_BYTE_COUNT_STAT |
+ CHDLC_RX_DATA_BYTE_COUNT_STAT);
+ cfg.max_CHDLC_data_field_length = card->wandev.mtu;
+
+ cfg.transmit_keepalive_timer = 0;
+ cfg.receive_keepalive_timer = 0;
+ cfg.keepalive_error_tolerance = 0;
+ cfg.SLARP_request_timer = 0;
+
+ cfg.IP_address = 0;
+ cfg.IP_netmask = 0;
+
+ return chdlc_configure(card, &cfg);
+}
+
+/*============================================================================
+ * Process global exception condition
+ */
+static int process_global_exception(sdla_t *card)
+{
+ CHDLC_MAILBOX_STRUCT* mbox = card->mbox;
+ int err;
+
+ mbox->buffer_length = 0;
+ mbox->command = READ_GLOBAL_EXCEPTION_CONDITION;
+ err = sdla_exec(mbox) ? mbox->return_code : CMD_TIMEOUT;
+
+ if(err != CMD_TIMEOUT ){
+
+ switch(mbox->return_code) {
+
+ case EXCEP_MODEM_STATUS_CHANGE:
+
+ printk(KERN_INFO "%s: Modem status change\n",
+ card->devname);
+
+ switch(mbox->data[0] & (DCD_HIGH | CTS_HIGH)) {
+ case (DCD_HIGH):
+ printk(KERN_INFO "%s: DCD high, CTS low\n",card->devname);
+ break;
+ case (CTS_HIGH):
+ printk(KERN_INFO "%s: DCD low, CTS high\n",card->devname);
+ break;
+ case ((DCD_HIGH | CTS_HIGH)):
+ printk(KERN_INFO "%s: DCD high, CTS high\n",card->devname);
+ break;
+ default:
+ printk(KERN_INFO "%s: DCD low, CTS low\n",card->devname);
+ break;
+ }
+
+ if (!(mbox->data[0] & DCD_HIGH) || !(mbox->data[0] & DCD_HIGH)){
+ //printk(KERN_INFO "Sending TERM Request Manually !\n");
+ send_ppp_term_request(card->wandev.dev);
+ }
+ break;
+
+ case EXCEP_TRC_DISABLED:
+ printk(KERN_INFO "%s: Line trace disabled\n",
+ card->devname);
+ break;
+
+ case EXCEP_IRQ_TIMEOUT:
+ printk(KERN_INFO "%s: IRQ timeout occurred\n",
+ card->devname);
+ break;
+
+ default:
+ printk(KERN_INFO "%s: Global exception %x\n",
+ card->devname, mbox->return_code);
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/*============================================================================
+ * Process chdlc exception condition
+ */
+static int process_chdlc_exception(sdla_t *card)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int err;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_EXCEPTION_CONDITION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if(err != CMD_TIMEOUT) {
+
+ switch (err) {
+
+ case EXCEP_LINK_ACTIVE:
+ port_set_state(card, WAN_CONNECTED);
+ break;
+
+ case EXCEP_LINK_INACTIVE_MODEM:
+ port_set_state(card, WAN_DISCONNECTED);
+ break;
+
+ case EXCEP_LOOPBACK_CONDITION:
+ printk(KERN_INFO "%s: Loopback Condition Detected.\n",
+ card->devname);
+ break;
+
+ case NO_CHDLC_EXCEP_COND_TO_REPORT:
+ printk(KERN_INFO "%s: No exceptions reported.\n",
+ card->devname);
+ break;
+ default:
+ printk(KERN_INFO "%s: Exception Condition %x!\n",
+ card->devname,err);
+ break;
+ }
+
+ }
+ return 0;
+}
+
+
+/*=============================================================================
+ * Store a UDP management packet for later processing.
+ */
+
+static int store_udp_mgmt_pkt(char udp_pkt_src, sdla_t* card,
+ struct sk_buff *skb, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area )
+{
+ int udp_pkt_stored = 0;
+
+ if(!chdlc_priv_area->udp_pkt_lgth &&
+ (skb->len <= MAX_LGTH_UDP_MGNT_PKT)) {
+ chdlc_priv_area->udp_pkt_lgth = skb->len;
+ chdlc_priv_area->udp_pkt_src = udp_pkt_src;
+ memcpy(chdlc_priv_area->udp_pkt_data, skb->data, skb->len);
+ chdlc_priv_area->timer_int_enabled = TMR_INT_ENABLED_UDP;
+ udp_pkt_stored = 1;
+ }
+
+ if(udp_pkt_src == UDP_PKT_FRM_STACK)
+ dev_kfree_skb_any(skb);
+ else
+ dev_kfree_skb_any(skb);
+
+ return(udp_pkt_stored);
+}
+
+
+/*=============================================================================
+ * Process UDP management packet.
+ */
+
+static int process_udp_mgmt_pkt(sdla_t* card, struct net_device* dev,
+ chdlc_private_area_t* chdlc_priv_area )
+{
+ unsigned char *buf;
+ unsigned int frames, len;
+ struct sk_buff *new_skb;
+ unsigned short buffer_length, real_len;
+ unsigned long data_ptr;
+ unsigned data_length;
+ int udp_mgmt_req_valid = 1;
+ CHDLC_MAILBOX_STRUCT *mb = card->mbox;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+ chdlc_udp_pkt_t *chdlc_udp_pkt;
+ struct timeval tv;
+ int err;
+ char ut_char;
+
+ chdlc_udp_pkt = (chdlc_udp_pkt_t *) chdlc_priv_area->udp_pkt_data;
+
+ if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+
+ switch(chdlc_udp_pkt->cblock.command) {
+ case READ_GLOBAL_STATISTICS:
+ case READ_MODEM_STATUS:
+ case READ_CHDLC_LINK_STATUS:
+ case CPIPE_ROUTER_UP_TIME:
+ case READ_COMMS_ERROR_STATS:
+ case READ_CHDLC_OPERATIONAL_STATS:
+
+ /* These two commands are executed for
+ * each request */
+ case READ_CHDLC_CONFIGURATION:
+ case READ_CHDLC_CODE_VERSION:
+ udp_mgmt_req_valid = 1;
+ break;
+ default:
+ udp_mgmt_req_valid = 0;
+ break;
+ }
+ }
+
+ if(!udp_mgmt_req_valid) {
+
+ /* set length to 0 */
+ chdlc_udp_pkt->cblock.buffer_length = 0;
+
+ /* set return code */
+ chdlc_udp_pkt->cblock.return_code = 0xCD;
+
+ if (net_ratelimit()){
+ printk(KERN_INFO
+ "%s: Warning, Illegal UDP command attempted from network: %x\n",
+ card->devname,chdlc_udp_pkt->cblock.command);
+ }
+
+ } else {
+ unsigned long trace_status_cfg_addr = 0;
+ TRACE_STATUS_EL_CFG_STRUCT trace_cfg_struct;
+ TRACE_STATUS_ELEMENT_STRUCT trace_element_struct;
+
+ switch(chdlc_udp_pkt->cblock.command) {
+
+ case CPIPE_ENABLE_TRACING:
+ if (!chdlc_priv_area->TracingEnabled) {
+
+ /* OPERATE_DATALINE_MONITOR */
+
+ mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+ mb->command = SET_TRACE_CONFIGURATION;
+
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+ trace_config = TRACE_ACTIVE;
+ /* Trace delay mode is not used because it slows
+ down transfer and results in a standoff situation
+ when there is a lot of data */
+
+ /* Configure the Trace based on user inputs */
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->trace_config |=
+ chdlc_udp_pkt->data[0];
+
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+ trace_deactivation_timer = 4000;
+
+
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ card->TracingEnabled = 0;
+ chdlc_udp_pkt->cblock.return_code = err;
+ mb->buffer_length = 0;
+ break;
+ }
+
+ /* Get the base address of the trace element list */
+ mb->buffer_length = 0;
+ mb->command = READ_TRACE_CONFIGURATION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if (err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ chdlc_priv_area->TracingEnabled = 0;
+ chdlc_udp_pkt->cblock.return_code = err;
+ mb->buffer_length = 0;
+ break;
+ }
+
+ trace_status_cfg_addr =((LINE_TRACE_CONFIG_STRUCT *)
+ mb->data) -> ptr_trace_stat_el_cfg_struct;
+
+ sdla_peek(&card->hw, trace_status_cfg_addr,
+ &trace_cfg_struct, sizeof(trace_cfg_struct));
+
+ chdlc_priv_area->start_trace_addr = trace_cfg_struct.
+ base_addr_trace_status_elements;
+
+ chdlc_priv_area->number_trace_elements =
+ trace_cfg_struct.number_trace_status_elements;
+
+ chdlc_priv_area->end_trace_addr = (unsigned long)
+ ((TRACE_STATUS_ELEMENT_STRUCT *)
+ chdlc_priv_area->start_trace_addr +
+ (chdlc_priv_area->number_trace_elements - 1));
+
+ chdlc_priv_area->base_addr_trace_buffer =
+ trace_cfg_struct.base_addr_trace_buffer;
+
+ chdlc_priv_area->end_addr_trace_buffer =
+ trace_cfg_struct.end_addr_trace_buffer;
+
+ chdlc_priv_area->curr_trace_addr =
+ trace_cfg_struct.next_trace_element_to_use;
+
+ chdlc_priv_area->available_buffer_space = 2000 -
+ sizeof(ip_pkt_t) -
+ sizeof(udp_pkt_t) -
+ sizeof(wp_mgmt_t) -
+ sizeof(cblock_t) -
+ sizeof(trace_info_t);
+ }
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ mb->buffer_length = 0;
+ chdlc_priv_area->TracingEnabled = 1;
+ break;
+
+
+ case CPIPE_DISABLE_TRACING:
+ if (chdlc_priv_area->TracingEnabled) {
+
+ /* OPERATE_DATALINE_MONITOR */
+ mb->buffer_length = sizeof(LINE_TRACE_CONFIG_STRUCT);
+ mb->command = SET_TRACE_CONFIGURATION;
+ ((LINE_TRACE_CONFIG_STRUCT *)mb->data)->
+ trace_config = TRACE_INACTIVE;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ }
+
+ chdlc_priv_area->TracingEnabled = 0;
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ mb->buffer_length = 0;
+ break;
+
+
+ case CPIPE_GET_TRACE_INFO:
+
+ if (!chdlc_priv_area->TracingEnabled) {
+ chdlc_udp_pkt->cblock.return_code = 1;
+ mb->buffer_length = 0;
+ break;
+ }
+
+ chdlc_udp_pkt->trace_info.ismoredata = 0x00;
+ buffer_length = 0; /* offset of packet already occupied */
+
+ for (frames=0; frames < chdlc_priv_area->number_trace_elements; frames++){
+
+ trace_pkt_t *trace_pkt = (trace_pkt_t *)
+ &chdlc_udp_pkt->data[buffer_length];
+
+ sdla_peek(&card->hw, chdlc_priv_area->curr_trace_addr,
+ (unsigned char *)&trace_element_struct,
+ sizeof(TRACE_STATUS_ELEMENT_STRUCT));
+
+ if (trace_element_struct.opp_flag == 0x00) {
+ break;
+ }
+
+ /* get pointer to real data */
+ data_ptr = trace_element_struct.ptr_data_bfr;
+
+ /* See if there is actual data on the trace buffer */
+ if (data_ptr){
+ data_length = trace_element_struct.trace_length;
+ }else{
+ data_length = 0;
+ chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+ }
+
+ if( (chdlc_priv_area->available_buffer_space - buffer_length)
+ < ( sizeof(trace_pkt_t) + data_length) ) {
+
+ /* indicate there are more frames on board & exit */
+ chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+ break;
+ }
+
+ trace_pkt->status = trace_element_struct.trace_type;
+
+ trace_pkt->time_stamp =
+ trace_element_struct.trace_time_stamp;
+
+ trace_pkt->real_length =
+ trace_element_struct.trace_length;
+
+ /* see if we can fit the frame into the user buffer */
+ real_len = trace_pkt->real_length;
+
+ if (data_ptr == 0) {
+ trace_pkt->data_avail = 0x00;
+ } else {
+ unsigned tmp = 0;
+
+ /* get the data from circular buffer
+ must check for end of buffer */
+ trace_pkt->data_avail = 0x01;
+
+ if ((data_ptr + real_len) >
+ chdlc_priv_area->end_addr_trace_buffer + 1){
+
+ tmp = chdlc_priv_area->end_addr_trace_buffer - data_ptr + 1;
+ sdla_peek(&card->hw, data_ptr,
+ trace_pkt->data,tmp);
+ data_ptr = chdlc_priv_area->base_addr_trace_buffer;
+ }
+
+ sdla_peek(&card->hw, data_ptr,
+ &trace_pkt->data[tmp], real_len - tmp);
+ }
+
+ /* zero the opp flag to show we got the frame */
+ ut_char = 0x00;
+ sdla_poke(&card->hw, chdlc_priv_area->curr_trace_addr, &ut_char, 1);
+
+ /* now move onto the next frame */
+ chdlc_priv_area->curr_trace_addr += sizeof(TRACE_STATUS_ELEMENT_STRUCT);
+
+ /* check if we went over the last address */
+ if ( chdlc_priv_area->curr_trace_addr > chdlc_priv_area->end_trace_addr ) {
+ chdlc_priv_area->curr_trace_addr = chdlc_priv_area->start_trace_addr;
+ }
+
+ if(trace_pkt->data_avail == 0x01) {
+ buffer_length += real_len - 1;
+ }
+
+ /* for the header */
+ buffer_length += sizeof(trace_pkt_t);
+
+ } /* For Loop */
+
+ if (frames == chdlc_priv_area->number_trace_elements){
+ chdlc_udp_pkt->trace_info.ismoredata = 0x01;
+ }
+ chdlc_udp_pkt->trace_info.num_frames = frames;
+
+ mb->buffer_length = buffer_length;
+ chdlc_udp_pkt->cblock.buffer_length = buffer_length;
+
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+
+ break;
+
+
+ case CPIPE_FT1_READ_STATUS:
+ ((unsigned char *)chdlc_udp_pkt->data )[0] =
+ flags->FT1_info_struct.parallel_port_A_input;
+
+ ((unsigned char *)chdlc_udp_pkt->data )[1] =
+ flags->FT1_info_struct.parallel_port_B_input;
+
+ chdlc_udp_pkt->cblock.return_code = COMMAND_OK;
+ mb->buffer_length = 2;
+ break;
+
+ case CPIPE_ROUTER_UP_TIME:
+ do_gettimeofday( &tv );
+ chdlc_priv_area->router_up_time = tv.tv_sec -
+ chdlc_priv_area->router_start_time;
+ *(unsigned long *)&chdlc_udp_pkt->data =
+ chdlc_priv_area->router_up_time;
+ mb->buffer_length = sizeof(unsigned long);
+ break;
+
+ case FT1_MONITOR_STATUS_CTRL:
+ /* Enable FT1 MONITOR STATUS */
+ if ((chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_STATUS) ||
+ (chdlc_udp_pkt->data[0] & ENABLE_READ_FT1_OP_STATS)) {
+
+ if( rCount++ != 0 ) {
+ chdlc_udp_pkt->cblock.
+ return_code = COMMAND_OK;
+ mb->buffer_length = 1;
+ break;
+ }
+ }
+
+ /* Disable FT1 MONITOR STATUS */
+ if( chdlc_udp_pkt->data[0] == 0) {
+
+ if( --rCount != 0) {
+ chdlc_udp_pkt->cblock.
+ return_code = COMMAND_OK;
+ mb->buffer_length = 1;
+ break;
+ }
+ }
+
+ default:
+ /* it's a board command */
+ mb->command = chdlc_udp_pkt->cblock.command;
+ mb->buffer_length = chdlc_udp_pkt->cblock.buffer_length;
+ if (mb->buffer_length) {
+ memcpy(&mb->data, (unsigned char *) chdlc_udp_pkt->
+ data, mb->buffer_length);
+ }
+ /* run the command on the board */
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ if (err != COMMAND_OK) {
+ break;
+ }
+
+ /* copy the result back to our buffer */
+ memcpy(&chdlc_udp_pkt->cblock, mb, sizeof(cblock_t));
+
+ if (mb->buffer_length) {
+ memcpy(&chdlc_udp_pkt->data, &mb->data,
+ mb->buffer_length);
+ }
+
+ } /* end of switch */
+ } /* end of else */
+
+ /* Fill UDP TTL */
+ chdlc_udp_pkt->ip_pkt.ttl = card->wandev.ttl;
+
+ len = reply_udp(chdlc_priv_area->udp_pkt_data, mb->buffer_length);
+
+ if(chdlc_priv_area->udp_pkt_src == UDP_PKT_FRM_NETWORK) {
+ if(!chdlc_send(card, chdlc_priv_area->udp_pkt_data, len)) {
+ ++ card->wandev.stats.tx_packets;
+ card->wandev.stats.tx_bytes += len;
+ }
+ } else {
+
+ /* Pass it up the stack
+ Allocate socket buffer */
+ if ((new_skb = dev_alloc_skb(len)) != NULL) {
+ /* copy data into new_skb */
+
+ buf = skb_put(new_skb, len);
+ memcpy(buf, chdlc_priv_area->udp_pkt_data, len);
+
+ /* Decapsulate pkt and pass it up the protocol stack */
+ new_skb->protocol = htons(ETH_P_IP);
+ new_skb->dev = dev;
+ new_skb->mac.raw = new_skb->data;
+
+ netif_rx(new_skb);
+ dev->last_rx = jiffies;
+ } else {
+
+ printk(KERN_INFO "%s: no socket buffers available!\n",
+ card->devname);
+ }
+ }
+
+ chdlc_priv_area->udp_pkt_lgth = 0;
+
+ return 0;
+}
+
+/*============================================================================
+ * Initialize Receive and Transmit Buffers.
+ */
+
+static void init_chdlc_tx_rx_buff(sdla_t* card, struct net_device *dev)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ CHDLC_TX_STATUS_EL_CFG_STRUCT *tx_config;
+ CHDLC_RX_STATUS_EL_CFG_STRUCT *rx_config;
+ char err;
+
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CONFIGURATION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+
+ if(err != COMMAND_OK) {
+ chdlc_error(card,err,mb);
+ return;
+ }
+
+ if(card->hw.type == SDLA_S514) {
+ tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Tx_stat_el_cfg_struct));
+ rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Rx_stat_el_cfg_struct));
+
+ /* Setup Head and Tails for buffers */
+ card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+ tx_config->base_addr_Tx_status_elements);
+ card->u.c.txbuf_last =
+ (CHDLC_DATA_TX_STATUS_EL_STRUCT *)
+ card->u.c.txbuf_base +
+ (tx_config->number_Tx_status_elements - 1);
+
+ card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+ rx_config->base_addr_Rx_status_elements);
+ card->u.c.rxbuf_last =
+ (CHDLC_DATA_RX_STATUS_EL_STRUCT *)
+ card->u.c.rxbuf_base +
+ (rx_config->number_Rx_status_elements - 1);
+
+ /* Set up next pointer to be used */
+ card->u.c.txbuf = (void *)(card->hw.dpmbase +
+ tx_config->next_Tx_status_element_to_use);
+ card->u.c.rxmb = (void *)(card->hw.dpmbase +
+ rx_config->next_Rx_status_element_to_use);
+ }
+ else {
+ tx_config = (CHDLC_TX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Tx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+ rx_config = (CHDLC_RX_STATUS_EL_CFG_STRUCT *)(card->hw.dpmbase +
+ (((CHDLC_CONFIGURATION_STRUCT *)mb->data)->
+ ptr_CHDLC_Rx_stat_el_cfg_struct % SDLA_WINDOWSIZE));
+
+ /* Setup Head and Tails for buffers */
+ card->u.c.txbuf_base = (void *)(card->hw.dpmbase +
+ (tx_config->base_addr_Tx_status_elements % SDLA_WINDOWSIZE));
+ card->u.c.txbuf_last =
+ (CHDLC_DATA_TX_STATUS_EL_STRUCT *)card->u.c.txbuf_base
+ + (tx_config->number_Tx_status_elements - 1);
+ card->u.c.rxbuf_base = (void *)(card->hw.dpmbase +
+ (rx_config->base_addr_Rx_status_elements % SDLA_WINDOWSIZE));
+ card->u.c.rxbuf_last =
+ (CHDLC_DATA_RX_STATUS_EL_STRUCT *)card->u.c.rxbuf_base
+ + (rx_config->number_Rx_status_elements - 1);
+
+ /* Set up next pointer to be used */
+ card->u.c.txbuf = (void *)(card->hw.dpmbase +
+ (tx_config->next_Tx_status_element_to_use % SDLA_WINDOWSIZE));
+ card->u.c.rxmb = (void *)(card->hw.dpmbase +
+ (rx_config->next_Rx_status_element_to_use % SDLA_WINDOWSIZE));
+ }
+
+ /* Setup Actual Buffer Start and end addresses */
+ card->u.c.rx_base = rx_config->base_addr_Rx_buffer;
+ card->u.c.rx_top = rx_config->end_addr_Rx_buffer;
+
+}
+
+/*=============================================================================
+ * Perform Interrupt Test by running READ_CHDLC_CODE_VERSION command MAX_INTR
+ * _TEST_COUNTER times.
+ */
+static int intr_test( sdla_t* card)
+{
+ CHDLC_MAILBOX_STRUCT* mb = card->mbox;
+ int err,i;
+
+ Intr_test_counter = 0;
+
+ /* The critical flag is unset because during initialization (if_open)
+ * we want the interrupts to be enabled so that when the wpc_isr is
+ * called it does not exit due to critical flag set.
+ */
+
+ err = chdlc_set_intr_mode(card, APP_INT_ON_COMMAND_COMPLETE);
+
+ if (err == CMD_OK) {
+ for (i = 0; i < MAX_INTR_TEST_COUNTER; i ++) {
+ mb->buffer_length = 0;
+ mb->command = READ_CHDLC_CODE_VERSION;
+ err = sdla_exec(mb) ? mb->return_code : CMD_TIMEOUT;
+ }
+ }
+ else {
+ return err;
+ }
+
+ err = chdlc_set_intr_mode(card, 0);
+
+ if (err != CMD_OK)
+ return err;
+
+ return 0;
+}
+
+/*==============================================================================
+ * Determine what type of UDP call it is. CPIPEAB ?
+ */
+static int udp_pkt_type(struct sk_buff *skb, sdla_t* card)
+{
+ chdlc_udp_pkt_t *chdlc_udp_pkt = (chdlc_udp_pkt_t *)skb->data;
+
+ if (!strncmp(chdlc_udp_pkt->wp_mgmt.signature,UDPMGMT_SIGNATURE,8) &&
+ (chdlc_udp_pkt->udp_pkt.udp_dst_port == ntohs(card->wandev.udp_port)) &&
+ (chdlc_udp_pkt->ip_pkt.protocol == UDPMGMT_UDP_PROTOCOL) &&
+ (chdlc_udp_pkt->wp_mgmt.request_reply == UDPMGMT_REQUEST)) {
+ return UDP_CPIPE_TYPE;
+ }
+ else return UDP_INVALID_TYPE;
+}
+
+/*============================================================================
+ * Set PORT state.
+ */
+static void port_set_state (sdla_t *card, int state)
+{
+ struct net_device *dev = card->wandev.dev;
+ chdlc_private_area_t *chdlc_priv_area = dev->priv;
+
+ if (card->u.c.state != state)
+ {
+ switch (state)
+ {
+ case WAN_CONNECTED:
+ printk (KERN_INFO "%s: HDLC link connected!\n",
+ card->devname);
+ break;
+
+ case WAN_CONNECTING:
+ printk (KERN_INFO "%s: HDLC link connecting...\n",
+ card->devname);
+ break;
+
+ case WAN_DISCONNECTED:
+ printk (KERN_INFO "%s: HDLC link disconnected!\n",
+ card->devname);
+ break;
+ }
+
+ card->wandev.state = card->u.c.state = state;
+ chdlc_priv_area->common.state = state;
+ }
+}
+
+void s508_lock (sdla_t *card, unsigned long *smp_flags)
+{
+ spin_lock_irqsave(&card->wandev.lock, *smp_flags);
+ if (card->next){
+ /* It is ok to use spin_lock here, since we
+ * already turned off interrupts */
+ spin_lock(&card->next->wandev.lock);
+ }
+}
+
+void s508_unlock (sdla_t *card, unsigned long *smp_flags)
+{
+ if (card->next){
+ spin_unlock(&card->next->wandev.lock);
+ }
+ spin_unlock_irqrestore(&card->wandev.lock, *smp_flags);
+}
+
+
+
+/*===========================================================================
+ * config_chdlc
+ *
+ * Configure the chdlc protocol and enable communications.
+ *
+ * The if_open() function binds this function to the poll routine.
+ * Therefore, this function will run every time the chdlc interface
+ * is brought up. We cannot run this function from the if_open
+ * because if_open does not have access to the remote IP address.
+ *
+ * If the communications are not enabled, proceed to configure
+ * the card and enable communications.
+ *
+ * If the communications are enabled, it means that the interface
+ * was shutdown by ether the user or driver. In this case, we
+ * have to check that the IP addresses have not changed. If
+ * the IP addresses have changed, we have to reconfigure the firmware
+ * and update the changed IP addresses. Otherwise, just exit.
+ *
+ */
+
+static int config_chdlc (sdla_t *card)
+{
+ struct net_device *dev = card->wandev.dev;
+ SHARED_MEMORY_INFO_STRUCT *flags = card->u.c.flags;
+
+ if (card->u.c.comm_enabled){
+ chdlc_comm_disable(card);
+ port_set_state(card, WAN_DISCONNECTED);
+ }
+
+ if (set_chdlc_config(card)) {
+ printk(KERN_INFO "%s: CHDLC Configuration Failed!\n",
+ card->devname);
+ return 0;
+ }
+ init_chdlc_tx_rx_buff(card, dev);
+
+ /* Set interrupt mode and mask */
+ if (chdlc_set_intr_mode(card, APP_INT_ON_RX_FRAME |
+ APP_INT_ON_GLOBAL_EXCEP_COND |
+ APP_INT_ON_TX_FRAME |
+ APP_INT_ON_CHDLC_EXCEP_COND | APP_INT_ON_TIMER)){
+ printk (KERN_INFO "%s: Failed to set interrupt triggers!\n",
+ card->devname);
+ return 0;
+ }
+
+
+ /* Mask the Transmit and Timer interrupt */
+ flags->interrupt_info_struct.interrupt_permission &=
+ ~(APP_INT_ON_TX_FRAME | APP_INT_ON_TIMER);
+
+
+ if (chdlc_comm_enable(card) != 0) {
+ printk(KERN_INFO "%s: Failed to enable chdlc communications!\n",
+ card->devname);
+ flags->interrupt_info_struct.interrupt_permission = 0;
+ card->u.c.comm_enabled=0;
+ chdlc_set_intr_mode(card,0);
+ return 0;
+ }
+
+ /* Initialize Rx/Tx buffer control fields */
+ port_set_state(card, WAN_CONNECTING);
+ return 0;
+}
+
+
+static void send_ppp_term_request(struct net_device *dev)
+{
+ struct sk_buff *new_skb;
+ unsigned char *buf;
+
+ if ((new_skb = dev_alloc_skb(8)) != NULL) {
+ /* copy data into new_skb */
+
+ buf = skb_put(new_skb, 8);
+ sprintf(buf,"%c%c%c%c%c%c%c%c", 0xFF,0x03,0xC0,0x21,0x05,0x98,0x00,0x07);
+
+ /* Decapsulate pkt and pass it up the protocol stack */
+ new_skb->protocol = htons(ETH_P_WAN_PPP);
+ new_skb->dev = dev;
+ new_skb->mac.raw = new_skb->data;
+
+ netif_rx(new_skb);
+ dev->last_rx = jiffies;
+ }
+}
+
+
+MODULE_LICENSE("GPL");
+
+/****** End ****************************************************************/
diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c
new file mode 100644
index 00000000000..1e7b47704ad
--- /dev/null
+++ b/drivers/net/wan/wanxl.c
@@ -0,0 +1,839 @@
+/*
+ * wanXL serial card driver for Linux
+ * host part
+ *
+ * Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * Status:
+ * - Only DTE (external clock) support with NRZ and NRZI encodings
+ * - wanXL100 will require minor driver modifications, no access to hw
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+
+#include "wanxl.h"
+
+static const char* version = "wanXL serial card driver version: 0.48";
+
+#define PLX_CTL_RESET 0x40000000 /* adapter reset */
+
+#undef DEBUG_PKT
+#undef DEBUG_PCI
+
+/* MAILBOX #1 - PUTS COMMANDS */
+#define MBX1_CMD_ABORTJ 0x85000000 /* Abort and Jump */
+#ifdef __LITTLE_ENDIAN
+#define MBX1_CMD_BSWAP 0x8C000001 /* little-endian Byte Swap Mode */
+#else
+#define MBX1_CMD_BSWAP 0x8C000000 /* big-endian Byte Swap Mode */
+#endif
+
+/* MAILBOX #2 - DRAM SIZE */
+#define MBX2_MEMSZ_MASK 0xFFFF0000 /* PUTS Memory Size Register mask */
+
+
+typedef struct {
+ struct net_device *dev;
+ struct card_t *card;
+ spinlock_t lock; /* for wanxl_xmit */
+ int node; /* physical port #0 - 3 */
+ unsigned int clock_type;
+ int tx_in, tx_out;
+ struct sk_buff *tx_skbs[TX_BUFFERS];
+}port_t;
+
+
+typedef struct {
+ desc_t rx_descs[RX_QUEUE_LENGTH];
+ port_status_t port_status[4];
+}card_status_t;
+
+
+typedef struct card_t {
+ int n_ports; /* 1, 2 or 4 ports */
+ u8 irq;
+
+ u8 __iomem *plx; /* PLX PCI9060 virtual base address */
+ struct pci_dev *pdev; /* for pci_name(pdev) */
+ int rx_in;
+ struct sk_buff *rx_skbs[RX_QUEUE_LENGTH];
+ card_status_t *status; /* shared between host and card */
+ dma_addr_t status_address;
+ port_t ports[0]; /* 1 - 4 port_t structures follow */
+}card_t;
+
+
+
+static inline port_t* dev_to_port(struct net_device *dev)
+{
+ return (port_t *)dev_to_hdlc(dev)->priv;
+}
+
+
+static inline port_status_t* get_status(port_t *port)
+{
+ return &port->card->status->port_status[port->node];
+}
+
+
+#ifdef DEBUG_PCI
+static inline dma_addr_t pci_map_single_debug(struct pci_dev *pdev, void *ptr,
+ size_t size, int direction)
+{
+ dma_addr_t addr = pci_map_single(pdev, ptr, size, direction);
+ if (addr + size > 0x100000000LL)
+ printk(KERN_CRIT "wanXL %s: pci_map_single() returned memory"
+ " at 0x%LX!\n", pci_name(pdev),
+ (unsigned long long)addr);
+ return addr;
+}
+
+#undef pci_map_single
+#define pci_map_single pci_map_single_debug
+#endif
+
+
+/* Cable and/or personality module change interrupt service */
+static inline void wanxl_cable_intr(port_t *port)
+{
+ u32 value = get_status(port)->cable;
+ int valid = 1;
+ const char *cable, *pm, *dte = "", *dsr = "", *dcd = "";
+
+ switch(value & 0x7) {
+ case STATUS_CABLE_V35: cable = "V.35"; break;
+ case STATUS_CABLE_X21: cable = "X.21"; break;
+ case STATUS_CABLE_V24: cable = "V.24"; break;
+ case STATUS_CABLE_EIA530: cable = "EIA530"; break;
+ case STATUS_CABLE_NONE: cable = "no"; break;
+ default: cable = "invalid";
+ }
+
+ switch((value >> STATUS_CABLE_PM_SHIFT) & 0x7) {
+ case STATUS_CABLE_V35: pm = "V.35"; break;
+ case STATUS_CABLE_X21: pm = "X.21"; break;
+ case STATUS_CABLE_V24: pm = "V.24"; break;
+ case STATUS_CABLE_EIA530: pm = "EIA530"; break;
+ case STATUS_CABLE_NONE: pm = "no personality"; valid = 0; break;
+ default: pm = "invalid personality"; valid = 0;
+ }
+
+ if (valid) {
+ if ((value & 7) == ((value >> STATUS_CABLE_PM_SHIFT) & 7)) {
+ dsr = (value & STATUS_CABLE_DSR) ? ", DSR ON" :
+ ", DSR off";
+ dcd = (value & STATUS_CABLE_DCD) ? ", carrier ON" :
+ ", carrier off";
+ }
+ dte = (value & STATUS_CABLE_DCE) ? " DCE" : " DTE";
+ }
+ printk(KERN_INFO "%s: %s%s module, %s cable%s%s\n",
+ port->dev->name, pm, dte, cable, dsr, dcd);
+
+ hdlc_set_carrier(value & STATUS_CABLE_DCD, port->dev);
+}
+
+
+
+/* Transmit complete interrupt service */
+static inline void wanxl_tx_intr(port_t *port)
+{
+ struct net_device *dev = port->dev;
+ struct net_device_stats *stats = hdlc_stats(dev);
+ while (1) {
+ desc_t *desc = &get_status(port)->tx_descs[port->tx_in];
+ struct sk_buff *skb = port->tx_skbs[port->tx_in];
+
+ switch (desc->stat) {
+ case PACKET_FULL:
+ case PACKET_EMPTY:
+ netif_wake_queue(dev);
+ return;
+
+ case PACKET_UNDERRUN:
+ stats->tx_errors++;
+ stats->tx_fifo_errors++;
+ break;
+
+ default:
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+ }
+ desc->stat = PACKET_EMPTY; /* Free descriptor */
+ pci_unmap_single(port->card->pdev, desc->address, skb->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb_irq(skb);
+ port->tx_in = (port->tx_in + 1) % TX_BUFFERS;
+ }
+}
+
+
+
+/* Receive complete interrupt service */
+static inline void wanxl_rx_intr(card_t *card)
+{
+ desc_t *desc;
+ while (desc = &card->status->rx_descs[card->rx_in],
+ desc->stat != PACKET_EMPTY) {
+ if ((desc->stat & PACKET_PORT_MASK) > card->n_ports)
+ printk(KERN_CRIT "wanXL %s: received packet for"
+ " nonexistent port\n", pci_name(card->pdev));
+ else {
+ struct sk_buff *skb = card->rx_skbs[card->rx_in];
+ port_t *port = &card->ports[desc->stat &
+ PACKET_PORT_MASK];
+ struct net_device *dev = port->dev;
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ if (!skb)
+ stats->rx_dropped++;
+ else {
+ pci_unmap_single(card->pdev, desc->address,
+ BUFFER_LENGTH,
+ PCI_DMA_FROMDEVICE);
+ skb_put(skb, desc->length);
+
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s RX(%i):", dev->name,
+ skb->len);
+ debug_frame(skb);
+#endif
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+ dev->last_rx = jiffies;
+ skb->protocol = hdlc_type_trans(skb, dev);
+ netif_rx(skb);
+ skb = NULL;
+ }
+
+ if (!skb) {
+ skb = dev_alloc_skb(BUFFER_LENGTH);
+ desc->address = skb ?
+ pci_map_single(card->pdev, skb->data,
+ BUFFER_LENGTH,
+ PCI_DMA_FROMDEVICE) : 0;
+ card->rx_skbs[card->rx_in] = skb;
+ }
+ }
+ desc->stat = PACKET_EMPTY; /* Free descriptor */
+ card->rx_in = (card->rx_in + 1) % RX_QUEUE_LENGTH;
+ }
+}
+
+
+
+static irqreturn_t wanxl_intr(int irq, void* dev_id, struct pt_regs *regs)
+{
+ card_t *card = dev_id;
+ int i;
+ u32 stat;
+ int handled = 0;
+
+
+ while((stat = readl(card->plx + PLX_DOORBELL_FROM_CARD)) != 0) {
+ handled = 1;
+ writel(stat, card->plx + PLX_DOORBELL_FROM_CARD);
+
+ for (i = 0; i < card->n_ports; i++) {
+ if (stat & (1 << (DOORBELL_FROM_CARD_TX_0 + i)))
+ wanxl_tx_intr(&card->ports[i]);
+ if (stat & (1 << (DOORBELL_FROM_CARD_CABLE_0 + i)))
+ wanxl_cable_intr(&card->ports[i]);
+ }
+ if (stat & (1 << DOORBELL_FROM_CARD_RX))
+ wanxl_rx_intr(card);
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+
+
+static int wanxl_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ desc_t *desc;
+
+ spin_lock(&port->lock);
+
+ desc = &get_status(port)->tx_descs[port->tx_out];
+ if (desc->stat != PACKET_EMPTY) {
+ /* should never happen - previous xmit should stop queue */
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
+#endif
+ netif_stop_queue(dev);
+ spin_unlock_irq(&port->lock);
+ return 1; /* request packet to be queued */
+ }
+
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s TX(%i):", dev->name, skb->len);
+ debug_frame(skb);
+#endif
+
+ port->tx_skbs[port->tx_out] = skb;
+ desc->address = pci_map_single(port->card->pdev, skb->data, skb->len,
+ PCI_DMA_TODEVICE);
+ desc->length = skb->len;
+ desc->stat = PACKET_FULL;
+ writel(1 << (DOORBELL_TO_CARD_TX_0 + port->node),
+ port->card->plx + PLX_DOORBELL_TO_CARD);
+ dev->trans_start = jiffies;
+
+ port->tx_out = (port->tx_out + 1) % TX_BUFFERS;
+
+ if (get_status(port)->tx_descs[port->tx_out].stat != PACKET_EMPTY) {
+ netif_stop_queue(dev);
+#ifdef DEBUG_PKT
+ printk(KERN_DEBUG "%s: transmitter buffer full\n", dev->name);
+#endif
+ }
+
+ spin_unlock(&port->lock);
+ return 0;
+}
+
+
+
+static int wanxl_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ port_t *port = dev_to_port(dev);
+
+ if (encoding != ENCODING_NRZ &&
+ encoding != ENCODING_NRZI)
+ return -EINVAL;
+
+ if (parity != PARITY_NONE &&
+ parity != PARITY_CRC32_PR1_CCITT &&
+ parity != PARITY_CRC16_PR1_CCITT &&
+ parity != PARITY_CRC32_PR0_CCITT &&
+ parity != PARITY_CRC16_PR0_CCITT)
+ return -EINVAL;
+
+ get_status(port)->encoding = encoding;
+ get_status(port)->parity = parity;
+ return 0;
+}
+
+
+
+static int wanxl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings line;
+ port_t *port = dev_to_port(dev);
+
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch (ifr->ifr_settings.type) {
+ case IF_GET_IFACE:
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+ line.clock_type = get_status(port)->clocking;
+ line.clock_rate = 0;
+ line.loopback = 0;
+
+ if (copy_to_user(ifr->ifr_settings.ifs_ifsu.sync, &line, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_SYNC_SERIAL:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+
+ if (copy_from_user(&line, ifr->ifr_settings.ifs_ifsu.sync,
+ size))
+ return -EFAULT;
+
+ if (line.clock_type != CLOCK_EXT &&
+ line.clock_type != CLOCK_TXFROMRX)
+ return -EINVAL; /* No such clock setting */
+
+ if (line.loopback != 0)
+ return -EINVAL;
+
+ get_status(port)->clocking = line.clock_type;
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+
+
+static int wanxl_open(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ u8 __iomem *dbr = port->card->plx + PLX_DOORBELL_TO_CARD;
+ unsigned long timeout;
+ int i;
+
+ if (get_status(port)->open) {
+ printk(KERN_ERR "%s: port already open\n", dev->name);
+ return -EIO;
+ }
+ if ((i = hdlc_open(dev)) != 0)
+ return i;
+
+ port->tx_in = port->tx_out = 0;
+ for (i = 0; i < TX_BUFFERS; i++)
+ get_status(port)->tx_descs[i].stat = PACKET_EMPTY;
+ /* signal the card */
+ writel(1 << (DOORBELL_TO_CARD_OPEN_0 + port->node), dbr);
+
+ timeout = jiffies + HZ;
+ do
+ if (get_status(port)->open) {
+ netif_start_queue(dev);
+ return 0;
+ }
+ while (time_after(timeout, jiffies));
+
+ printk(KERN_ERR "%s: unable to open port\n", dev->name);
+ /* ask the card to close the port, should it be still alive */
+ writel(1 << (DOORBELL_TO_CARD_CLOSE_0 + port->node), dbr);
+ return -EFAULT;
+}
+
+
+
+static int wanxl_close(struct net_device *dev)
+{
+ port_t *port = dev_to_port(dev);
+ unsigned long timeout;
+ int i;
+
+ hdlc_close(dev);
+ /* signal the card */
+ writel(1 << (DOORBELL_TO_CARD_CLOSE_0 + port->node),
+ port->card->plx + PLX_DOORBELL_TO_CARD);
+
+ timeout = jiffies + HZ;
+ do
+ if (!get_status(port)->open)
+ break;
+ while (time_after(timeout, jiffies));
+
+ if (get_status(port)->open)
+ printk(KERN_ERR "%s: unable to close port\n", dev->name);
+
+ netif_stop_queue(dev);
+
+ for (i = 0; i < TX_BUFFERS; i++) {
+ desc_t *desc = &get_status(port)->tx_descs[i];
+
+ if (desc->stat != PACKET_EMPTY) {
+ desc->stat = PACKET_EMPTY;
+ pci_unmap_single(port->card->pdev, desc->address,
+ port->tx_skbs[i]->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb(port->tx_skbs[i]);
+ }
+ }
+ return 0;
+}
+
+
+
+static struct net_device_stats *wanxl_get_stats(struct net_device *dev)
+{
+ struct net_device_stats *stats = hdlc_stats(dev);
+ port_t *port = dev_to_port(dev);
+
+ stats->rx_over_errors = get_status(port)->rx_overruns;
+ stats->rx_frame_errors = get_status(port)->rx_frame_errors;
+ stats->rx_errors = stats->rx_over_errors + stats->rx_frame_errors;
+ return stats;
+}
+
+
+
+static int wanxl_puts_command(card_t *card, u32 cmd)
+{
+ unsigned long timeout = jiffies + 5 * HZ;
+
+ writel(cmd, card->plx + PLX_MAILBOX_1);
+ do {
+ if (readl(card->plx + PLX_MAILBOX_1) == 0)
+ return 0;
+
+ schedule();
+ }while (time_after(timeout, jiffies));
+
+ return -1;
+}
+
+
+
+static void wanxl_reset(card_t *card)
+{
+ u32 old_value = readl(card->plx + PLX_CONTROL) & ~PLX_CTL_RESET;
+
+ writel(0x80, card->plx + PLX_MAILBOX_0);
+ writel(old_value | PLX_CTL_RESET, card->plx + PLX_CONTROL);
+ readl(card->plx + PLX_CONTROL); /* wait for posted write */
+ udelay(1);
+ writel(old_value, card->plx + PLX_CONTROL);
+ readl(card->plx + PLX_CONTROL); /* wait for posted write */
+}
+
+
+
+static void wanxl_pci_remove_one(struct pci_dev *pdev)
+{
+ card_t *card = pci_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < card->n_ports; i++) {
+ unregister_hdlc_device(card->ports[i].dev);
+ free_netdev(card->ports[i].dev);
+ }
+
+ /* unregister and free all host resources */
+ if (card->irq)
+ free_irq(card->irq, card);
+
+ wanxl_reset(card);
+
+ for (i = 0; i < RX_QUEUE_LENGTH; i++)
+ if (card->rx_skbs[i]) {
+ pci_unmap_single(card->pdev,
+ card->status->rx_descs[i].address,
+ BUFFER_LENGTH, PCI_DMA_FROMDEVICE);
+ dev_kfree_skb(card->rx_skbs[i]);
+ }
+
+ if (card->plx)
+ iounmap(card->plx);
+
+ if (card->status)
+ pci_free_consistent(pdev, sizeof(card_status_t),
+ card->status, card->status_address);
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ pci_set_drvdata(pdev, NULL);
+ kfree(card);
+}
+
+
+#include "wanxlfw.inc"
+
+static int __devinit wanxl_pci_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ card_t *card;
+ u32 ramsize, stat;
+ unsigned long timeout;
+ u32 plx_phy; /* PLX PCI base address */
+ u32 mem_phy; /* memory PCI base addr */
+ u8 __iomem *mem; /* memory virtual base addr */
+ int i, ports, alloc_size;
+
+#ifndef MODULE
+ static int printed_version;
+ if (!printed_version) {
+ printed_version++;
+ printk(KERN_INFO "%s\n", version);
+ }
+#endif
+
+ i = pci_enable_device(pdev);
+ if (i)
+ return i;
+
+ /* QUICC can only access first 256 MB of host RAM directly,
+ but PLX9060 DMA does 32-bits for actual packet data transfers */
+
+ /* FIXME when PCI/DMA subsystems are fixed.
+ We set both dma_mask and consistent_dma_mask to 28 bits
+ and pray pci_alloc_consistent() will use this info. It should
+ work on most platforms */
+ if (pci_set_consistent_dma_mask(pdev, 0x0FFFFFFF) ||
+ pci_set_dma_mask(pdev, 0x0FFFFFFF)) {
+ printk(KERN_ERR "wanXL: No usable DMA configuration\n");
+ return -EIO;
+ }
+
+ i = pci_request_regions(pdev, "wanXL");
+ if (i) {
+ pci_disable_device(pdev);
+ return i;
+ }
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_SBE_WANXL100: ports = 1; break;
+ case PCI_DEVICE_ID_SBE_WANXL200: ports = 2; break;
+ default: ports = 4;
+ }
+
+ alloc_size = sizeof(card_t) + ports * sizeof(port_t);
+ card = kmalloc(alloc_size, GFP_KERNEL);
+ if (card == NULL) {
+ printk(KERN_ERR "wanXL %s: unable to allocate memory\n",
+ pci_name(pdev));
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ return -ENOBUFS;
+ }
+ memset(card, 0, alloc_size);
+
+ pci_set_drvdata(pdev, card);
+ card->pdev = pdev;
+
+ card->status = pci_alloc_consistent(pdev, sizeof(card_status_t),
+ &card->status_address);
+ if (card->status == NULL) {
+ wanxl_pci_remove_one(pdev);
+ return -ENOBUFS;
+ }
+
+#ifdef DEBUG_PCI
+ printk(KERN_DEBUG "wanXL %s: pci_alloc_consistent() returned memory"
+ " at 0x%LX\n", pci_name(pdev),
+ (unsigned long long)card->status_address);
+#endif
+
+ /* FIXME when PCI/DMA subsystems are fixed.
+ We set both dma_mask and consistent_dma_mask back to 32 bits
+ to indicate the card can do 32-bit DMA addressing */
+ if (pci_set_consistent_dma_mask(pdev, 0xFFFFFFFF) ||
+ pci_set_dma_mask(pdev, 0xFFFFFFFF)) {
+ printk(KERN_ERR "wanXL: No usable DMA configuration\n");
+ wanxl_pci_remove_one(pdev);
+ return -EIO;
+ }
+
+ /* set up PLX mapping */
+ plx_phy = pci_resource_start(pdev, 0);
+ card->plx = ioremap_nocache(plx_phy, 0x70);
+
+#if RESET_WHILE_LOADING
+ wanxl_reset(card);
+#endif
+
+ timeout = jiffies + 20 * HZ;
+ while ((stat = readl(card->plx + PLX_MAILBOX_0)) != 0) {
+ if (time_before(timeout, jiffies)) {
+ printk(KERN_WARNING "wanXL %s: timeout waiting for"
+ " PUTS to complete\n", pci_name(pdev));
+ wanxl_pci_remove_one(pdev);
+ return -ENODEV;
+ }
+
+ switch(stat & 0xC0) {
+ case 0x00: /* hmm - PUTS completed with non-zero code? */
+ case 0x80: /* PUTS still testing the hardware */
+ break;
+
+ default:
+ printk(KERN_WARNING "wanXL %s: PUTS test 0x%X"
+ " failed\n", pci_name(pdev), stat & 0x30);
+ wanxl_pci_remove_one(pdev);
+ return -ENODEV;
+ }
+
+ schedule();
+ }
+
+ /* get on-board memory size (PUTS detects no more than 4 MB) */
+ ramsize = readl(card->plx + PLX_MAILBOX_2) & MBX2_MEMSZ_MASK;
+
+ /* set up on-board RAM mapping */
+ mem_phy = pci_resource_start(pdev, 2);
+
+
+ /* sanity check the board's reported memory size */
+ if (ramsize < BUFFERS_ADDR +
+ (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * ports) {
+ printk(KERN_WARNING "wanXL %s: no enough on-board RAM"
+ " (%u bytes detected, %u bytes required)\n",
+ pci_name(pdev), ramsize, BUFFERS_ADDR +
+ (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * ports);
+ wanxl_pci_remove_one(pdev);
+ return -ENODEV;
+ }
+
+ if (wanxl_puts_command(card, MBX1_CMD_BSWAP)) {
+ printk(KERN_WARNING "wanXL %s: unable to Set Byte Swap"
+ " Mode\n", pci_name(pdev));
+ wanxl_pci_remove_one(pdev);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < RX_QUEUE_LENGTH; i++) {
+ struct sk_buff *skb = dev_alloc_skb(BUFFER_LENGTH);
+ card->rx_skbs[i] = skb;
+ if (skb)
+ card->status->rx_descs[i].address =
+ pci_map_single(card->pdev, skb->data,
+ BUFFER_LENGTH,
+ PCI_DMA_FROMDEVICE);
+ }
+
+ mem = ioremap_nocache(mem_phy, PDM_OFFSET + sizeof(firmware));
+ for (i = 0; i < sizeof(firmware); i += 4)
+ writel(htonl(*(u32*)(firmware + i)), mem + PDM_OFFSET + i);
+
+ for (i = 0; i < ports; i++)
+ writel(card->status_address +
+ (void *)&card->status->port_status[i] -
+ (void *)card->status, mem + PDM_OFFSET + 4 + i * 4);
+ writel(card->status_address, mem + PDM_OFFSET + 20);
+ writel(PDM_OFFSET, mem);
+ iounmap(mem);
+
+ writel(0, card->plx + PLX_MAILBOX_5);
+
+ if (wanxl_puts_command(card, MBX1_CMD_ABORTJ)) {
+ printk(KERN_WARNING "wanXL %s: unable to Abort and Jump\n",
+ pci_name(pdev));
+ wanxl_pci_remove_one(pdev);
+ return -ENODEV;
+ }
+
+ stat = 0;
+ timeout = jiffies + 5 * HZ;
+ do {
+ if ((stat = readl(card->plx + PLX_MAILBOX_5)) != 0)
+ break;
+ schedule();
+ }while (time_after(timeout, jiffies));
+
+ if (!stat) {
+ printk(KERN_WARNING "wanXL %s: timeout while initializing card"
+ "firmware\n", pci_name(pdev));
+ wanxl_pci_remove_one(pdev);
+ return -ENODEV;
+ }
+
+#if DETECT_RAM
+ ramsize = stat;
+#endif
+
+ printk(KERN_INFO "wanXL %s: at 0x%X, %u KB of RAM at 0x%X, irq %u\n",
+ pci_name(pdev), plx_phy, ramsize / 1024, mem_phy, pdev->irq);
+
+ /* Allocate IRQ */
+ if (request_irq(pdev->irq, wanxl_intr, SA_SHIRQ, "wanXL", card)) {
+ printk(KERN_WARNING "wanXL %s: could not allocate IRQ%i.\n",
+ pci_name(pdev), pdev->irq);
+ wanxl_pci_remove_one(pdev);
+ return -EBUSY;
+ }
+ card->irq = pdev->irq;
+
+ for (i = 0; i < ports; i++) {
+ hdlc_device *hdlc;
+ port_t *port = &card->ports[i];
+ struct net_device *dev = alloc_hdlcdev(port);
+ if (!dev) {
+ printk(KERN_ERR "wanXL %s: unable to allocate"
+ " memory\n", pci_name(pdev));
+ wanxl_pci_remove_one(pdev);
+ return -ENOMEM;
+ }
+
+ port->dev = dev;
+ hdlc = dev_to_hdlc(dev);
+ spin_lock_init(&port->lock);
+ SET_MODULE_OWNER(dev);
+ dev->tx_queue_len = 50;
+ dev->do_ioctl = wanxl_ioctl;
+ dev->open = wanxl_open;
+ dev->stop = wanxl_close;
+ hdlc->attach = wanxl_attach;
+ hdlc->xmit = wanxl_xmit;
+ dev->get_stats = wanxl_get_stats;
+ port->card = card;
+ port->node = i;
+ get_status(port)->clocking = CLOCK_EXT;
+ if (register_hdlc_device(dev)) {
+ printk(KERN_ERR "wanXL %s: unable to register hdlc"
+ " device\n", pci_name(pdev));
+ free_netdev(dev);
+ wanxl_pci_remove_one(pdev);
+ return -ENOBUFS;
+ }
+ card->n_ports++;
+ }
+
+ printk(KERN_INFO "wanXL %s: port", pci_name(pdev));
+ for (i = 0; i < ports; i++)
+ printk("%s #%i: %s", i ? "," : "", i,
+ card->ports[i].dev->name);
+ printk("\n");
+
+ for (i = 0; i < ports; i++)
+ wanxl_cable_intr(&card->ports[i]); /* get carrier status etc.*/
+
+ return 0;
+}
+
+static struct pci_device_id wanxl_pci_tbl[] __devinitdata = {
+ { PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_SBE_WANXL100, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, 0 },
+ { PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_SBE_WANXL200, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, 0 },
+ { PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_SBE_WANXL400, PCI_ANY_ID,
+ PCI_ANY_ID, 0, 0, 0 },
+ { 0, }
+};
+
+
+static struct pci_driver wanxl_pci_driver = {
+ .name = "wanXL",
+ .id_table = wanxl_pci_tbl,
+ .probe = wanxl_pci_init_one,
+ .remove = wanxl_pci_remove_one,
+};
+
+
+static int __init wanxl_init_module(void)
+{
+#ifdef MODULE
+ printk(KERN_INFO "%s\n", version);
+#endif
+ return pci_module_init(&wanxl_pci_driver);
+}
+
+static void __exit wanxl_cleanup_module(void)
+{
+ pci_unregister_driver(&wanxl_pci_driver);
+}
+
+
+MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
+MODULE_DESCRIPTION("SBE Inc. wanXL serial port driver");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(pci, wanxl_pci_tbl);
+
+module_init(wanxl_init_module);
+module_exit(wanxl_cleanup_module);
diff --git a/drivers/net/wan/wanxl.h b/drivers/net/wan/wanxl.h
new file mode 100644
index 00000000000..3f86558f8a6
--- /dev/null
+++ b/drivers/net/wan/wanxl.h
@@ -0,0 +1,152 @@
+/*
+ * wanXL serial card driver for Linux
+ * definitions common to host driver and card firmware
+ *
+ * Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#define RESET_WHILE_LOADING 0
+
+/* you must rebuild the firmware if any of the following is changed */
+#define DETECT_RAM 0 /* needed for > 4MB RAM, 16 MB maximum */
+#define QUICC_MEMCPY_USES_PLX 1 /* must be used if the host has > 256 MB RAM */
+
+
+#define STATUS_CABLE_V35 2
+#define STATUS_CABLE_X21 3
+#define STATUS_CABLE_V24 4
+#define STATUS_CABLE_EIA530 5
+#define STATUS_CABLE_INVALID 6
+#define STATUS_CABLE_NONE 7
+
+#define STATUS_CABLE_DCE 0x8000
+#define STATUS_CABLE_DSR 0x0010
+#define STATUS_CABLE_DCD 0x0008
+#define STATUS_CABLE_PM_SHIFT 5
+
+#define PDM_OFFSET 0x1000
+
+#define TX_BUFFERS 10 /* per port */
+#define RX_BUFFERS 30
+#define RX_QUEUE_LENGTH 40 /* card->host queue length - per card */
+
+#define PACKET_EMPTY 0x00
+#define PACKET_FULL 0x10
+#define PACKET_SENT 0x20 /* TX only */
+#define PACKET_UNDERRUN 0x30 /* TX only */
+#define PACKET_PORT_MASK 0x03 /* RX only */
+
+/* bit numbers in PLX9060 doorbell registers */
+#define DOORBELL_FROM_CARD_TX_0 0 /* packet sent by the card */
+#define DOORBELL_FROM_CARD_TX_1 1
+#define DOORBELL_FROM_CARD_TX_2 2
+#define DOORBELL_FROM_CARD_TX_3 3
+#define DOORBELL_FROM_CARD_RX 4
+#define DOORBELL_FROM_CARD_CABLE_0 5 /* cable/PM/etc. changed */
+#define DOORBELL_FROM_CARD_CABLE_1 6
+#define DOORBELL_FROM_CARD_CABLE_2 7
+#define DOORBELL_FROM_CARD_CABLE_3 8
+
+#define DOORBELL_TO_CARD_OPEN_0 0
+#define DOORBELL_TO_CARD_OPEN_1 1
+#define DOORBELL_TO_CARD_OPEN_2 2
+#define DOORBELL_TO_CARD_OPEN_3 3
+#define DOORBELL_TO_CARD_CLOSE_0 4
+#define DOORBELL_TO_CARD_CLOSE_1 5
+#define DOORBELL_TO_CARD_CLOSE_2 6
+#define DOORBELL_TO_CARD_CLOSE_3 7
+#define DOORBELL_TO_CARD_TX_0 8 /* outbound packet queued */
+#define DOORBELL_TO_CARD_TX_1 9
+#define DOORBELL_TO_CARD_TX_2 10
+#define DOORBELL_TO_CARD_TX_3 11
+
+/* firmware-only status bits, starting from last DOORBELL_TO_CARD + 1 */
+#define TASK_SCC_0 12
+#define TASK_SCC_1 13
+#define TASK_SCC_2 14
+#define TASK_SCC_3 15
+
+#define ALIGN32(x) (((x) + 3) & 0xFFFFFFFC)
+#define BUFFER_LENGTH ALIGN32(HDLC_MAX_MRU + 4) /* 4 bytes for 32-bit CRC */
+
+/* Address of TX and RX buffers in 68360 address space */
+#define BUFFERS_ADDR 0x4000 /* 16 KB */
+
+#ifndef __ASSEMBLER__
+#define PLX_OFFSET 0
+#else
+#define PLX_OFFSET PLX + 0x80
+#endif
+
+#define PLX_MAILBOX_0 (PLX_OFFSET + 0x40)
+#define PLX_MAILBOX_1 (PLX_OFFSET + 0x44)
+#define PLX_MAILBOX_2 (PLX_OFFSET + 0x48)
+#define PLX_MAILBOX_3 (PLX_OFFSET + 0x4C)
+#define PLX_MAILBOX_4 (PLX_OFFSET + 0x50)
+#define PLX_MAILBOX_5 (PLX_OFFSET + 0x54)
+#define PLX_MAILBOX_6 (PLX_OFFSET + 0x58)
+#define PLX_MAILBOX_7 (PLX_OFFSET + 0x5C)
+#define PLX_DOORBELL_TO_CARD (PLX_OFFSET + 0x60)
+#define PLX_DOORBELL_FROM_CARD (PLX_OFFSET + 0x64)
+#define PLX_INTERRUPT_CS (PLX_OFFSET + 0x68)
+#define PLX_CONTROL (PLX_OFFSET + 0x6C)
+
+#ifdef __ASSEMBLER__
+#define PLX_DMA_0_MODE (PLX + 0x100)
+#define PLX_DMA_0_PCI (PLX + 0x104)
+#define PLX_DMA_0_LOCAL (PLX + 0x108)
+#define PLX_DMA_0_LENGTH (PLX + 0x10C)
+#define PLX_DMA_0_DESC (PLX + 0x110)
+#define PLX_DMA_1_MODE (PLX + 0x114)
+#define PLX_DMA_1_PCI (PLX + 0x118)
+#define PLX_DMA_1_LOCAL (PLX + 0x11C)
+#define PLX_DMA_1_LENGTH (PLX + 0x120)
+#define PLX_DMA_1_DESC (PLX + 0x124)
+#define PLX_DMA_CMD_STS (PLX + 0x128)
+#define PLX_DMA_ARBITR_0 (PLX + 0x12C)
+#define PLX_DMA_ARBITR_1 (PLX + 0x130)
+#endif
+
+#define DESC_LENGTH 12
+
+/* offsets from start of status_t */
+/* card to host */
+#define STATUS_OPEN 0
+#define STATUS_CABLE (STATUS_OPEN + 4)
+#define STATUS_RX_OVERRUNS (STATUS_CABLE + 4)
+#define STATUS_RX_FRAME_ERRORS (STATUS_RX_OVERRUNS + 4)
+
+/* host to card */
+#define STATUS_PARITY (STATUS_RX_FRAME_ERRORS + 4)
+#define STATUS_ENCODING (STATUS_PARITY + 4)
+#define STATUS_CLOCKING (STATUS_ENCODING + 4)
+#define STATUS_TX_DESCS (STATUS_CLOCKING + 4)
+
+#ifndef __ASSEMBLER__
+
+typedef struct {
+ volatile u32 stat;
+ u32 address; /* PCI address */
+ volatile u32 length;
+}desc_t;
+
+
+typedef struct {
+// Card to host
+ volatile u32 open;
+ volatile u32 cable;
+ volatile u32 rx_overruns;
+ volatile u32 rx_frame_errors;
+
+// Host to card
+ u32 parity;
+ u32 encoding;
+ u32 clocking;
+ desc_t tx_descs[TX_BUFFERS];
+}port_status_t;
+
+#endif /* __ASSEMBLER__ */
diff --git a/drivers/net/wan/wanxlfw.S b/drivers/net/wan/wanxlfw.S
new file mode 100644
index 00000000000..73aae2bf2f1
--- /dev/null
+++ b/drivers/net/wan/wanxlfw.S
@@ -0,0 +1,895 @@
+.psize 0
+/*
+ wanXL serial card driver for Linux
+ card firmware part
+
+ Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of version 2 of the GNU General Public License
+ as published by the Free Software Foundation.
+
+
+
+
+ DPRAM BDs:
+ 0x000 - 0x050 TX#0 0x050 - 0x140 RX#0
+ 0x140 - 0x190 TX#1 0x190 - 0x280 RX#1
+ 0x280 - 0x2D0 TX#2 0x2D0 - 0x3C0 RX#2
+ 0x3C0 - 0x410 TX#3 0x410 - 0x500 RX#3
+
+
+ 000 5FF 1536 Bytes Dual-Port RAM User Data / BDs
+ 600 6FF 256 Bytes Dual-Port RAM User Data / BDs
+ 700 7FF 256 Bytes Dual-Port RAM User Data / BDs
+ C00 CBF 192 Bytes Dual-Port RAM Parameter RAM Page 1
+ D00 DBF 192 Bytes Dual-Port RAM Parameter RAM Page 2
+ E00 EBF 192 Bytes Dual-Port RAM Parameter RAM Page 3
+ F00 FBF 192 Bytes Dual-Port RAM Parameter RAM Page 4
+
+ local interrupts level
+ NMI 7
+ PIT timer, CPM (RX/TX complete) 4
+ PCI9060 DMA and PCI doorbells 3
+ Cable - not used 1
+*/
+
+#include <linux/hdlc.h>
+#include "wanxl.h"
+
+/* memory addresses and offsets */
+
+MAX_RAM_SIZE = 16 * 1024 * 1024 // max RAM supported by hardware
+
+PCI9060_VECTOR = 0x0000006C
+CPM_IRQ_BASE = 0x40
+ERROR_VECTOR = CPM_IRQ_BASE * 4
+SCC1_VECTOR = (CPM_IRQ_BASE + 0x1E) * 4
+SCC2_VECTOR = (CPM_IRQ_BASE + 0x1D) * 4
+SCC3_VECTOR = (CPM_IRQ_BASE + 0x1C) * 4
+SCC4_VECTOR = (CPM_IRQ_BASE + 0x1B) * 4
+CPM_IRQ_LEVEL = 4
+TIMER_IRQ = 128
+TIMER_IRQ_LEVEL = 4
+PITR_CONST = 0x100 + 16 // 1 Hz timer
+
+MBAR = 0x0003FF00
+
+VALUE_WINDOW = 0x40000000
+ORDER_WINDOW = 0xC0000000
+
+PLX = 0xFFF90000
+
+CSRA = 0xFFFB0000
+CSRB = 0xFFFB0002
+CSRC = 0xFFFB0004
+CSRD = 0xFFFB0006
+STATUS_CABLE_LL = 0x2000
+STATUS_CABLE_DTR = 0x1000
+
+DPRBASE = 0xFFFC0000
+
+SCC1_BASE = DPRBASE + 0xC00
+MISC_BASE = DPRBASE + 0xCB0
+SCC2_BASE = DPRBASE + 0xD00
+SCC3_BASE = DPRBASE + 0xE00
+SCC4_BASE = DPRBASE + 0xF00
+
+// offset from SCCx_BASE
+// SCC_xBASE contain offsets from DPRBASE and must be divisible by 8
+SCC_RBASE = 0 // 16-bit RxBD base address
+SCC_TBASE = 2 // 16-bit TxBD base address
+SCC_RFCR = 4 // 8-bit Rx function code
+SCC_TFCR = 5 // 8-bit Tx function code
+SCC_MRBLR = 6 // 16-bit maximum Rx buffer length
+SCC_C_MASK = 0x34 // 32-bit CRC constant
+SCC_C_PRES = 0x38 // 32-bit CRC preset
+SCC_MFLR = 0x46 // 16-bit max Rx frame length (without flags)
+
+REGBASE = DPRBASE + 0x1000
+PICR = REGBASE + 0x026 // 16-bit periodic irq control
+PITR = REGBASE + 0x02A // 16-bit periodic irq timing
+OR1 = REGBASE + 0x064 // 32-bit RAM bank #1 options
+CICR = REGBASE + 0x540 // 32(24)-bit CP interrupt config
+CIMR = REGBASE + 0x548 // 32-bit CP interrupt mask
+CISR = REGBASE + 0x54C // 32-bit CP interrupts in-service
+PADIR = REGBASE + 0x550 // 16-bit PortA data direction bitmap
+PAPAR = REGBASE + 0x552 // 16-bit PortA pin assignment bitmap
+PAODR = REGBASE + 0x554 // 16-bit PortA open drain bitmap
+PADAT = REGBASE + 0x556 // 16-bit PortA data register
+
+PCDIR = REGBASE + 0x560 // 16-bit PortC data direction bitmap
+PCPAR = REGBASE + 0x562 // 16-bit PortC pin assignment bitmap
+PCSO = REGBASE + 0x564 // 16-bit PortC special options
+PCDAT = REGBASE + 0x566 // 16-bit PortC data register
+PCINT = REGBASE + 0x568 // 16-bit PortC interrupt control
+CR = REGBASE + 0x5C0 // 16-bit Command register
+
+SCC1_REGS = REGBASE + 0x600
+SCC2_REGS = REGBASE + 0x620
+SCC3_REGS = REGBASE + 0x640
+SCC4_REGS = REGBASE + 0x660
+SICR = REGBASE + 0x6EC // 32-bit SI clock route
+
+// offset from SCCx_REGS
+SCC_GSMR_L = 0x00 // 32 bits
+SCC_GSMR_H = 0x04 // 32 bits
+SCC_PSMR = 0x08 // 16 bits
+SCC_TODR = 0x0C // 16 bits
+SCC_DSR = 0x0E // 16 bits
+SCC_SCCE = 0x10 // 16 bits
+SCC_SCCM = 0x14 // 16 bits
+SCC_SCCS = 0x17 // 8 bits
+
+#if QUICC_MEMCPY_USES_PLX
+ .macro memcpy_from_pci src, dest, len // len must be < 8 MB
+ addl #3, \len
+ andl #0xFFFFFFFC, \len // always copy n * 4 bytes
+ movel \src, PLX_DMA_0_PCI
+ movel \dest, PLX_DMA_0_LOCAL
+ movel \len, PLX_DMA_0_LENGTH
+ movel #0x0103, PLX_DMA_CMD_STS // start channel 0 transfer
+ bsr memcpy_from_pci_run
+ .endm
+
+ .macro memcpy_to_pci src, dest, len
+ addl #3, \len
+ andl #0xFFFFFFFC, \len // always copy n * 4 bytes
+ movel \src, PLX_DMA_1_LOCAL
+ movel \dest, PLX_DMA_1_PCI
+ movel \len, PLX_DMA_1_LENGTH
+ movel #0x0301, PLX_DMA_CMD_STS // start channel 1 transfer
+ bsr memcpy_to_pci_run
+ .endm
+
+#else
+
+ .macro memcpy src, dest, len // len must be < 65536 bytes
+ movel %d7, -(%sp) // src and dest must be < 256 MB
+ movel \len, %d7 // bits 0 and 1
+ lsrl #2, \len
+ andl \len, \len
+ beq 99f // only 0 - 3 bytes
+ subl #1, \len // for dbf
+98: movel (\src)+, (\dest)+
+ dbfw \len, 98b
+99: movel %d7, \len
+ btstl #1, \len
+ beq 99f
+ movew (\src)+, (\dest)+
+99: btstl #0, \len
+ beq 99f
+ moveb (\src)+, (\dest)+
+99:
+ movel (%sp)+, %d7
+ .endm
+
+ .macro memcpy_from_pci src, dest, len
+ addl #VALUE_WINDOW, \src
+ memcpy \src, \dest, \len
+ .endm
+
+ .macro memcpy_to_pci src, dest, len
+ addl #VALUE_WINDOW, \dest
+ memcpy \src, \dest, \len
+ .endm
+#endif
+
+
+ .macro wait_for_command
+99: btstl #0, CR
+ bne 99b
+ .endm
+
+
+
+
+/****************************** card initialization *******************/
+ .text
+ .global _start
+_start: bra init
+
+ .org _start + 4
+ch_status_addr: .long 0, 0, 0, 0
+rx_descs_addr: .long 0
+
+init:
+#if DETECT_RAM
+ movel OR1, %d0
+ andl #0xF00007FF, %d0 // mask AMxx bits
+ orl #0xFFFF800 & ~(MAX_RAM_SIZE - 1), %d0 // update RAM bank size
+ movel %d0, OR1
+#endif
+
+ addl #VALUE_WINDOW, rx_descs_addr // PCI addresses of shared data
+ clrl %d0 // D0 = 4 * port
+init_1: tstl ch_status_addr(%d0)
+ beq init_2
+ addl #VALUE_WINDOW, ch_status_addr(%d0)
+init_2: addl #4, %d0
+ cmpl #4 * 4, %d0
+ bne init_1
+
+ movel #pci9060_interrupt, PCI9060_VECTOR
+ movel #error_interrupt, ERROR_VECTOR
+ movel #port_interrupt_1, SCC1_VECTOR
+ movel #port_interrupt_2, SCC2_VECTOR
+ movel #port_interrupt_3, SCC3_VECTOR
+ movel #port_interrupt_4, SCC4_VECTOR
+ movel #timer_interrupt, TIMER_IRQ * 4
+
+ movel #0x78000000, CIMR // only SCCx IRQs from CPM
+ movew #(TIMER_IRQ_LEVEL << 8) + TIMER_IRQ, PICR // interrupt from PIT
+ movew #PITR_CONST, PITR
+
+ // SCC1=SCCa SCC2=SCCb SCC3=SCCc SCC4=SCCd prio=4 HP=-1 IRQ=64-79
+ movel #0xD41F40 + (CPM_IRQ_LEVEL << 13), CICR
+ movel #0x543, PLX_DMA_0_MODE // 32-bit, Ready, Burst, IRQ
+ movel #0x543, PLX_DMA_1_MODE
+ movel #0x0, PLX_DMA_0_DESC // from PCI to local
+ movel #0x8, PLX_DMA_1_DESC // from local to PCI
+ movel #0x101, PLX_DMA_CMD_STS // enable both DMA channels
+ // enable local IRQ, DMA, doorbells and PCI IRQ
+ orl #0x000F0300, PLX_INTERRUPT_CS
+
+#if DETECT_RAM
+ bsr ram_test
+#else
+ movel #1, PLX_MAILBOX_5 // non-zero value = init complete
+#endif
+ bsr check_csr
+
+ movew #0xFFFF, PAPAR // all pins are clocks/data
+ clrw PADIR // first function
+ clrw PCSO // CD and CTS always active
+
+
+/****************************** main loop *****************************/
+
+main: movel channel_stats, %d7 // D7 = doorbell + irq status
+ clrl channel_stats
+
+ tstl %d7
+ bne main_1
+ // nothing to do - wait for next event
+ stop #0x2200 // supervisor + IRQ level 2
+ movew #0x2700, %sr // disable IRQs again
+ bra main
+
+main_1: clrl %d0 // D0 = 4 * port
+ clrl %d6 // D6 = doorbell to host value
+
+main_l: btstl #DOORBELL_TO_CARD_CLOSE_0, %d7
+ beq main_op
+ bclrl #DOORBELL_TO_CARD_OPEN_0, %d7 // in case both bits are set
+ bsr close_port
+main_op:
+ btstl #DOORBELL_TO_CARD_OPEN_0, %d7
+ beq main_cl
+ bsr open_port
+main_cl:
+ btstl #DOORBELL_TO_CARD_TX_0, %d7
+ beq main_txend
+ bsr tx
+main_txend:
+ btstl #TASK_SCC_0, %d7
+ beq main_next
+ bsr tx_end
+ bsr rx
+
+main_next:
+ lsrl #1, %d7 // port status for next port
+ addl #4, %d0 // D0 = 4 * next port
+ cmpl #4 * 4, %d0
+ bne main_l
+ movel %d6, PLX_DOORBELL_FROM_CARD // signal the host
+ bra main
+
+
+/****************************** open port *****************************/
+
+open_port: // D0 = 4 * port, D6 = doorbell to host
+ movel ch_status_addr(%d0), %a0 // A0 = port status address
+ tstl STATUS_OPEN(%a0)
+ bne open_port_ret // port already open
+ movel #1, STATUS_OPEN(%a0) // confirm the port is open
+// setup BDs
+ clrl tx_in(%d0)
+ clrl tx_out(%d0)
+ clrl tx_count(%d0)
+ clrl rx_in(%d0)
+
+ movel SICR, %d1 // D1 = clock settings in SICR
+ andl clocking_mask(%d0), %d1
+ cmpl #CLOCK_TXFROMRX, STATUS_CLOCKING(%a0)
+ bne open_port_clock_ext
+ orl clocking_txfromrx(%d0), %d1
+ bra open_port_set_clock
+
+open_port_clock_ext:
+ orl clocking_ext(%d0), %d1
+open_port_set_clock:
+ movel %d1, SICR // update clock settings in SICR
+
+ orw #STATUS_CABLE_DTR, csr_output(%d0) // DTR on
+ bsr check_csr // call with disabled timer interrupt
+
+// Setup TX descriptors
+ movel first_buffer(%d0), %d1 // D1 = starting buffer address
+ movel tx_first_bd(%d0), %a1 // A1 = starting TX BD address
+ movel #TX_BUFFERS - 2, %d2 // D2 = TX_BUFFERS - 1 counter
+ movel #0x18000000, %d3 // D3 = initial TX BD flags: Int + Last
+ cmpl #PARITY_NONE, STATUS_PARITY(%a0)
+ beq open_port_tx_loop
+ bsetl #26, %d3 // TX BD flag: Transmit CRC
+open_port_tx_loop:
+ movel %d3, (%a1)+ // TX flags + length
+ movel %d1, (%a1)+ // buffer address
+ addl #BUFFER_LENGTH, %d1
+ dbfw %d2, open_port_tx_loop
+
+ bsetl #29, %d3 // TX BD flag: Wrap (last BD)
+ movel %d3, (%a1)+ // Final TX flags + length
+ movel %d1, (%a1)+ // buffer address
+
+// Setup RX descriptors // A1 = starting RX BD address
+ movel #RX_BUFFERS - 2, %d2 // D2 = RX_BUFFERS - 1 counter
+open_port_rx_loop:
+ movel #0x90000000, (%a1)+ // RX flags + length
+ movel %d1, (%a1)+ // buffer address
+ addl #BUFFER_LENGTH, %d1
+ dbfw %d2, open_port_rx_loop
+
+ movel #0xB0000000, (%a1)+ // Final RX flags + length
+ movel %d1, (%a1)+ // buffer address
+
+// Setup port parameters
+ movel scc_base_addr(%d0), %a1 // A1 = SCC_BASE address
+ movel scc_reg_addr(%d0), %a2 // A2 = SCC_REGS address
+
+ movel #0xFFFF, SCC_SCCE(%a2) // clear status bits
+ movel #0x0000, SCC_SCCM(%a2) // interrupt mask
+
+ movel tx_first_bd(%d0), %d1
+ movew %d1, SCC_TBASE(%a1) // D1 = offset of first TxBD
+ addl #TX_BUFFERS * 8, %d1
+ movew %d1, SCC_RBASE(%a1) // D1 = offset of first RxBD
+ moveb #0x8, SCC_RFCR(%a1) // Intel mode, 1000
+ moveb #0x8, SCC_TFCR(%a1)
+
+// Parity settings
+ cmpl #PARITY_CRC16_PR1_CCITT, STATUS_PARITY(%a0)
+ bne open_port_parity_1
+ clrw SCC_PSMR(%a2) // CRC16-CCITT
+ movel #0xF0B8, SCC_C_MASK(%a1)
+ movel #0xFFFF, SCC_C_PRES(%a1)
+ movew #HDLC_MAX_MRU + 2, SCC_MFLR(%a1) // 2 bytes for CRC
+ movew #2, parity_bytes(%d0)
+ bra open_port_2
+
+open_port_parity_1:
+ cmpl #PARITY_CRC32_PR1_CCITT, STATUS_PARITY(%a0)
+ bne open_port_parity_2
+ movew #0x0800, SCC_PSMR(%a2) // CRC32-CCITT
+ movel #0xDEBB20E3, SCC_C_MASK(%a1)
+ movel #0xFFFFFFFF, SCC_C_PRES(%a1)
+ movew #HDLC_MAX_MRU + 4, SCC_MFLR(%a1) // 4 bytes for CRC
+ movew #4, parity_bytes(%d0)
+ bra open_port_2
+
+open_port_parity_2:
+ cmpl #PARITY_CRC16_PR0_CCITT, STATUS_PARITY(%a0)
+ bne open_port_parity_3
+ clrw SCC_PSMR(%a2) // CRC16-CCITT preset 0
+ movel #0xF0B8, SCC_C_MASK(%a1)
+ clrl SCC_C_PRES(%a1)
+ movew #HDLC_MAX_MRU + 2, SCC_MFLR(%a1) // 2 bytes for CRC
+ movew #2, parity_bytes(%d0)
+ bra open_port_2
+
+open_port_parity_3:
+ cmpl #PARITY_CRC32_PR0_CCITT, STATUS_PARITY(%a0)
+ bne open_port_parity_4
+ movew #0x0800, SCC_PSMR(%a2) // CRC32-CCITT preset 0
+ movel #0xDEBB20E3, SCC_C_MASK(%a1)
+ clrl SCC_C_PRES(%a1)
+ movew #HDLC_MAX_MRU + 4, SCC_MFLR(%a1) // 4 bytes for CRC
+ movew #4, parity_bytes(%d0)
+ bra open_port_2
+
+open_port_parity_4:
+ clrw SCC_PSMR(%a2) // no parity
+ movel #0xF0B8, SCC_C_MASK(%a1)
+ movel #0xFFFF, SCC_C_PRES(%a1)
+ movew #HDLC_MAX_MRU, SCC_MFLR(%a1) // 0 bytes for CRC
+ clrw parity_bytes(%d0)
+
+open_port_2:
+ movel #0x00000003, SCC_GSMR_H(%a2) // RTSM
+ cmpl #ENCODING_NRZI, STATUS_ENCODING(%a0)
+ bne open_port_nrz
+ movel #0x10040900, SCC_GSMR_L(%a2) // NRZI: TCI Tend RECN+TENC=1
+ bra open_port_3
+
+open_port_nrz:
+ movel #0x10040000, SCC_GSMR_L(%a2) // NRZ: TCI Tend RECN+TENC=0
+open_port_3:
+ movew #BUFFER_LENGTH, SCC_MRBLR(%a1)
+ movel %d0, %d1
+ lsll #4, %d1 // D1 bits 7 and 6 = port
+ orl #1, %d1
+ movew %d1, CR // Init SCC RX and TX params
+ wait_for_command
+
+ // TCI Tend ENR ENT
+ movew #0x001F, SCC_SCCM(%a2) // TXE RXF BSY TXB RXB interrupts
+ orl #0x00000030, SCC_GSMR_L(%a2) // enable SCC
+open_port_ret:
+ rts
+
+
+/****************************** close port ****************************/
+
+close_port: // D0 = 4 * port, D6 = doorbell to host
+ movel scc_reg_addr(%d0), %a0 // A0 = SCC_REGS address
+ clrw SCC_SCCM(%a0) // no SCC interrupts
+ andl #0xFFFFFFCF, SCC_GSMR_L(%a0) // Disable ENT and ENR
+
+ andw #~STATUS_CABLE_DTR, csr_output(%d0) // DTR off
+ bsr check_csr // call with disabled timer interrupt
+
+ movel ch_status_addr(%d0), %d1
+ clrl STATUS_OPEN(%d1) // confirm the port is closed
+ rts
+
+
+/****************************** transmit packet ***********************/
+// queue packets for transmission
+tx: // D0 = 4 * port, D6 = doorbell to host
+ cmpl #TX_BUFFERS, tx_count(%d0)
+ beq tx_ret // all DB's = descs in use
+
+ movel tx_out(%d0), %d1
+ movel %d1, %d2 // D1 = D2 = tx_out BD# = desc#
+ mulul #DESC_LENGTH, %d2 // D2 = TX desc offset
+ addl ch_status_addr(%d0), %d2
+ addl #STATUS_TX_DESCS, %d2 // D2 = TX desc address
+ cmpl #PACKET_FULL, (%d2) // desc status
+ bne tx_ret
+
+// queue it
+ movel 4(%d2), %a0 // PCI address
+ lsll #3, %d1 // BD is 8-bytes long
+ addl tx_first_bd(%d0), %d1 // D1 = current tx_out BD addr
+
+ movel 4(%d1), %a1 // A1 = dest address
+ movel 8(%d2), %d2 // D2 = length
+ movew %d2, 2(%d1) // length into BD
+ memcpy_from_pci %a0, %a1, %d2
+ bsetl #31, (%d1) // CP go ahead
+
+// update tx_out and tx_count
+ movel tx_out(%d0), %d1
+ addl #1, %d1
+ cmpl #TX_BUFFERS, %d1
+ bne tx_1
+ clrl %d1
+tx_1: movel %d1, tx_out(%d0)
+
+ addl #1, tx_count(%d0)
+ bra tx
+
+tx_ret: rts
+
+
+/****************************** packet received ***********************/
+
+// Service receive buffers // D0 = 4 * port, D6 = doorbell to host
+rx: movel rx_in(%d0), %d1 // D1 = rx_in BD#
+ lsll #3, %d1 // BD is 8-bytes long
+ addl rx_first_bd(%d0), %d1 // D1 = current rx_in BD address
+ movew (%d1), %d2 // D2 = RX BD flags
+ btstl #15, %d2
+ bne rx_ret // BD still empty
+
+ btstl #1, %d2
+ bne rx_overrun
+
+ tstw parity_bytes(%d0)
+ bne rx_parity
+ bclrl #2, %d2 // do not test for CRC errors
+rx_parity:
+ andw #0x0CBC, %d2 // mask status bits
+ cmpw #0x0C00, %d2 // correct frame
+ bne rx_bad_frame
+ clrl %d3
+ movew 2(%d1), %d3
+ subw parity_bytes(%d0), %d3 // D3 = packet length
+ cmpw #HDLC_MAX_MRU, %d3
+ bgt rx_bad_frame
+
+rx_good_frame:
+ movel rx_out, %d2
+ mulul #DESC_LENGTH, %d2
+ addl rx_descs_addr, %d2 // D2 = RX desc address
+ cmpl #PACKET_EMPTY, (%d2) // desc stat
+ bne rx_overrun
+
+ movel %d3, 8(%d2)
+ movel 4(%d1), %a0 // A0 = source address
+ movel 4(%d2), %a1
+ tstl %a1
+ beq rx_ignore_data
+ memcpy_to_pci %a0, %a1, %d3
+rx_ignore_data:
+ movel packet_full(%d0), (%d2) // update desc stat
+
+// update D6 and rx_out
+ bsetl #DOORBELL_FROM_CARD_RX, %d6 // signal host that RX completed
+ movel rx_out, %d2
+ addl #1, %d2
+ cmpl #RX_QUEUE_LENGTH, %d2
+ bne rx_1
+ clrl %d2
+rx_1: movel %d2, rx_out
+
+rx_free_bd:
+ andw #0xF000, (%d1) // clear CM and error bits
+ bsetl #31, (%d1) // free BD
+// update rx_in
+ movel rx_in(%d0), %d1
+ addl #1, %d1
+ cmpl #RX_BUFFERS, %d1
+ bne rx_2
+ clrl %d1
+rx_2: movel %d1, rx_in(%d0)
+ bra rx
+
+rx_overrun:
+ movel ch_status_addr(%d0), %d2
+ addl #1, STATUS_RX_OVERRUNS(%d2)
+ bra rx_free_bd
+
+rx_bad_frame:
+ movel ch_status_addr(%d0), %d2
+ addl #1, STATUS_RX_FRAME_ERRORS(%d2)
+ bra rx_free_bd
+
+rx_ret: rts
+
+
+/****************************** packet transmitted ********************/
+
+// Service transmit buffers // D0 = 4 * port, D6 = doorbell to host
+tx_end: tstl tx_count(%d0)
+ beq tx_end_ret // TX buffers already empty
+
+ movel tx_in(%d0), %d1
+ movel %d1, %d2 // D1 = D2 = tx_in BD# = desc#
+ lsll #3, %d1 // BD is 8-bytes long
+ addl tx_first_bd(%d0), %d1 // D1 = current tx_in BD address
+ movew (%d1), %d3 // D3 = TX BD flags
+ btstl #15, %d3
+ bne tx_end_ret // BD still being transmitted
+
+// update D6, tx_in and tx_count
+ orl bell_tx(%d0), %d6 // signal host that TX desc freed
+ subl #1, tx_count(%d0)
+ movel tx_in(%d0), %d1
+ addl #1, %d1
+ cmpl #TX_BUFFERS, %d1
+ bne tx_end_1
+ clrl %d1
+tx_end_1:
+ movel %d1, tx_in(%d0)
+
+// free host's descriptor
+ mulul #DESC_LENGTH, %d2 // D2 = TX desc offset
+ addl ch_status_addr(%d0), %d2
+ addl #STATUS_TX_DESCS, %d2 // D2 = TX desc address
+ btstl #1, %d3
+ bne tx_end_underrun
+ movel #PACKET_SENT, (%d2)
+ bra tx_end
+
+tx_end_underrun:
+ movel #PACKET_UNDERRUN, (%d2)
+ bra tx_end
+
+tx_end_ret: rts
+
+
+/****************************** PLX PCI9060 DMA memcpy ****************/
+
+#if QUICC_MEMCPY_USES_PLX
+// called with interrupts disabled
+memcpy_from_pci_run:
+ movel %d0, -(%sp)
+ movew %sr, -(%sp)
+memcpy_1:
+ movel PLX_DMA_CMD_STS, %d0 // do not btst PLX register directly
+ btstl #4, %d0 // transfer done?
+ bne memcpy_end
+ stop #0x2200 // enable PCI9060 interrupts
+ movew #0x2700, %sr // disable interrupts again
+ bra memcpy_1
+
+memcpy_to_pci_run:
+ movel %d0, -(%sp)
+ movew %sr, -(%sp)
+memcpy_2:
+ movel PLX_DMA_CMD_STS, %d0 // do not btst PLX register directly
+ btstl #12, %d0 // transfer done?
+ bne memcpy_end
+ stop #0x2200 // enable PCI9060 interrupts
+ movew #0x2700, %sr // disable interrupts again
+ bra memcpy_2
+
+memcpy_end:
+ movew (%sp)+, %sr
+ movel (%sp)+, %d0
+ rts
+#endif
+
+
+
+
+
+
+/****************************** PLX PCI9060 interrupt *****************/
+
+pci9060_interrupt:
+ movel %d0, -(%sp)
+
+ movel PLX_DOORBELL_TO_CARD, %d0
+ movel %d0, PLX_DOORBELL_TO_CARD // confirm all requests
+ orl %d0, channel_stats
+
+ movel #0x0909, PLX_DMA_CMD_STS // clear DMA ch #0 and #1 interrupts
+
+ movel (%sp)+, %d0
+ rte
+
+/****************************** SCC interrupts ************************/
+
+port_interrupt_1:
+ orl #0, SCC1_REGS + SCC_SCCE; // confirm SCC events
+ orl #1 << TASK_SCC_0, channel_stats
+ movel #0x40000000, CISR
+ rte
+
+port_interrupt_2:
+ orl #0, SCC2_REGS + SCC_SCCE; // confirm SCC events
+ orl #1 << TASK_SCC_1, channel_stats
+ movel #0x20000000, CISR
+ rte
+
+port_interrupt_3:
+ orl #0, SCC3_REGS + SCC_SCCE; // confirm SCC events
+ orl #1 << TASK_SCC_2, channel_stats
+ movel #0x10000000, CISR
+ rte
+
+port_interrupt_4:
+ orl #0, SCC4_REGS + SCC_SCCE; // confirm SCC events
+ orl #1 << TASK_SCC_3, channel_stats
+ movel #0x08000000, CISR
+ rte
+
+error_interrupt:
+ rte
+
+
+/****************************** cable and PM routine ******************/
+// modified registers: none
+check_csr:
+ movel %d0, -(%sp)
+ movel %d1, -(%sp)
+ movel %d2, -(%sp)
+ movel %a0, -(%sp)
+ movel %a1, -(%sp)
+
+ clrl %d0 // D0 = 4 * port
+ movel #CSRA, %a0 // A0 = CSR address
+
+check_csr_loop:
+ movew (%a0), %d1 // D1 = CSR input bits
+ andl #0xE7, %d1 // PM and cable sense bits (no DCE bit)
+ cmpw #STATUS_CABLE_V35 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+ bne check_csr_1
+ movew #0x0E08, %d1
+ bra check_csr_valid
+
+check_csr_1:
+ cmpw #STATUS_CABLE_X21 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+ bne check_csr_2
+ movew #0x0408, %d1
+ bra check_csr_valid
+
+check_csr_2:
+ cmpw #STATUS_CABLE_V24 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+ bne check_csr_3
+ movew #0x0208, %d1
+ bra check_csr_valid
+
+check_csr_3:
+ cmpw #STATUS_CABLE_EIA530 * (1 + 1 << STATUS_CABLE_PM_SHIFT), %d1
+ bne check_csr_disable
+ movew #0x0D08, %d1
+ bra check_csr_valid
+
+check_csr_disable:
+ movew #0x0008, %d1 // D1 = disable everything
+ movew #0x80E7, %d2 // D2 = input mask: ignore DSR
+ bra check_csr_write
+
+check_csr_valid: // D1 = mode and IRQ bits
+ movew csr_output(%d0), %d2
+ andw #0x3000, %d2 // D2 = requested LL and DTR bits
+ orw %d2, %d1 // D1 = all requested output bits
+ movew #0x80FF, %d2 // D2 = input mask: include DSR
+
+check_csr_write:
+ cmpw old_csr_output(%d0), %d1
+ beq check_csr_input
+ movew %d1, old_csr_output(%d0)
+ movew %d1, (%a0) // Write CSR output bits
+
+check_csr_input:
+ movew (PCDAT), %d1
+ andw dcd_mask(%d0), %d1
+ beq check_csr_dcd_on // DCD and CTS signals are negated
+ movew (%a0), %d1 // D1 = CSR input bits
+ andw #~STATUS_CABLE_DCD, %d1 // DCD off
+ bra check_csr_previous
+
+check_csr_dcd_on:
+ movew (%a0), %d1 // D1 = CSR input bits
+ orw #STATUS_CABLE_DCD, %d1 // DCD on
+check_csr_previous:
+ andw %d2, %d1 // input mask
+ movel ch_status_addr(%d0), %a1
+ cmpl STATUS_CABLE(%a1), %d1 // check for change
+ beq check_csr_next
+ movel %d1, STATUS_CABLE(%a1) // update status
+ movel bell_cable(%d0), PLX_DOORBELL_FROM_CARD // signal the host
+
+check_csr_next:
+ addl #2, %a0 // next CSR register
+ addl #4, %d0 // D0 = 4 * next port
+ cmpl #4 * 4, %d0
+ bne check_csr_loop
+
+ movel (%sp)+, %a1
+ movel (%sp)+, %a0
+ movel (%sp)+, %d2
+ movel (%sp)+, %d1
+ movel (%sp)+, %d0
+ rts
+
+
+/****************************** timer interrupt ***********************/
+
+timer_interrupt:
+ bsr check_csr
+ rte
+
+
+/****************************** RAM sizing and test *******************/
+#if DETECT_RAM
+ram_test:
+ movel #0x12345678, %d1 // D1 = test value
+ movel %d1, (128 * 1024 - 4)
+ movel #128 * 1024, %d0 // D0 = RAM size tested
+ram_test_size:
+ cmpl #MAX_RAM_SIZE, %d0
+ beq ram_test_size_found
+ movel %d0, %a0
+ addl #128 * 1024 - 4, %a0
+ cmpl (%a0), %d1
+ beq ram_test_size_check
+ram_test_next_size:
+ lsll #1, %d0
+ bra ram_test_size
+
+ram_test_size_check:
+ eorl #0xFFFFFFFF, %d1
+ movel %d1, (128 * 1024 - 4)
+ cmpl (%a0), %d1
+ bne ram_test_next_size
+
+ram_test_size_found: // D0 = RAM size
+ movel %d0, %a0 // A0 = fill ptr
+ subl #firmware_end + 4, %d0
+ lsrl #2, %d0
+ movel %d0, %d1 // D1 = DBf counter
+ram_test_fill:
+ movel %a0, -(%a0)
+ dbfw %d1, ram_test_fill
+ subl #0x10000, %d1
+ cmpl #0xFFFFFFFF, %d1
+ bne ram_test_fill
+
+ram_test_loop: // D0 = DBf counter
+ cmpl (%a0)+, %a0
+ dbnew %d0, ram_test_loop
+ bne ram_test_found_bad
+ subl #0x10000, %d0
+ cmpl #0xFFFFFFFF, %d0
+ bne ram_test_loop
+ bra ram_test_all_ok
+
+ram_test_found_bad:
+ subl #4, %a0
+ram_test_all_ok:
+ movel %a0, PLX_MAILBOX_5
+ rts
+#endif
+
+
+/****************************** constants *****************************/
+
+scc_reg_addr:
+ .long SCC1_REGS, SCC2_REGS, SCC3_REGS, SCC4_REGS
+scc_base_addr:
+ .long SCC1_BASE, SCC2_BASE, SCC3_BASE, SCC4_BASE
+
+tx_first_bd:
+ .long DPRBASE
+ .long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8
+ .long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8 * 2
+ .long DPRBASE + (TX_BUFFERS + RX_BUFFERS) * 8 * 3
+
+rx_first_bd:
+ .long DPRBASE + TX_BUFFERS * 8
+ .long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8
+ .long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8 * 2
+ .long DPRBASE + TX_BUFFERS * 8 + (TX_BUFFERS + RX_BUFFERS) * 8 * 3
+
+first_buffer:
+ .long BUFFERS_ADDR
+ .long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH
+ .long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * 2
+ .long BUFFERS_ADDR + (TX_BUFFERS + RX_BUFFERS) * BUFFER_LENGTH * 3
+
+bell_tx:
+ .long 1 << DOORBELL_FROM_CARD_TX_0, 1 << DOORBELL_FROM_CARD_TX_1
+ .long 1 << DOORBELL_FROM_CARD_TX_2, 1 << DOORBELL_FROM_CARD_TX_3
+
+bell_cable:
+ .long 1 << DOORBELL_FROM_CARD_CABLE_0, 1 << DOORBELL_FROM_CARD_CABLE_1
+ .long 1 << DOORBELL_FROM_CARD_CABLE_2, 1 << DOORBELL_FROM_CARD_CABLE_3
+
+packet_full:
+ .long PACKET_FULL, PACKET_FULL + 1, PACKET_FULL + 2, PACKET_FULL + 3
+
+clocking_ext:
+ .long 0x0000002C, 0x00003E00, 0x002C0000, 0x3E000000
+clocking_txfromrx:
+ .long 0x0000002D, 0x00003F00, 0x002D0000, 0x3F000000
+clocking_mask:
+ .long 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
+dcd_mask:
+ .word 0x020, 0, 0x080, 0, 0x200, 0, 0x800
+
+ .ascii "wanXL firmware\n"
+ .asciz "Copyright (C) 2003 Krzysztof Halasa <khc@pm.waw.pl>\n"
+
+
+/****************************** variables *****************************/
+
+ .align 4
+channel_stats: .long 0
+
+tx_in: .long 0, 0, 0, 0 // transmitted
+tx_out: .long 0, 0, 0, 0 // received from host for transmission
+tx_count: .long 0, 0, 0, 0 // currently in transmit queue
+
+rx_in: .long 0, 0, 0, 0 // received from port
+rx_out: .long 0 // transmitted to host
+parity_bytes: .word 0, 0, 0, 0, 0, 0, 0 // only 4 words are used
+
+csr_output: .word 0
+old_csr_output: .word 0, 0, 0, 0, 0, 0, 0
+ .align 4
+firmware_end: // must be dword-aligned
diff --git a/drivers/net/wan/wanxlfw.inc_shipped b/drivers/net/wan/wanxlfw.inc_shipped
new file mode 100644
index 00000000000..73da688f943
--- /dev/null
+++ b/drivers/net/wan/wanxlfw.inc_shipped
@@ -0,0 +1,158 @@
+static u8 firmware[]={
+0x60,0x00,0x00,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xB9,0x40,0x00,0x00,0x00,0x00,0x00,
+0x10,0x14,0x42,0x80,0x4A,0xB0,0x09,0xB0,0x00,0x00,0x10,0x04,0x67,0x00,0x00,0x0E,
+0x06,0xB0,0x40,0x00,0x00,0x00,0x09,0xB0,0x00,0x00,0x10,0x04,0x58,0x80,0x0C,0x80,
+0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0xDE,0x21,0xFC,0x00,0x00,0x16,0xBC,0x00,0x6C,
+0x21,0xFC,0x00,0x00,0x17,0x5E,0x01,0x00,0x21,0xFC,0x00,0x00,0x16,0xDE,0x01,0x78,
+0x21,0xFC,0x00,0x00,0x16,0xFE,0x01,0x74,0x21,0xFC,0x00,0x00,0x17,0x1E,0x01,0x70,
+0x21,0xFC,0x00,0x00,0x17,0x3E,0x01,0x6C,0x21,0xFC,0x00,0x00,0x18,0x4C,0x02,0x00,
+0x23,0xFC,0x78,0x00,0x00,0x00,0xFF,0xFC,0x15,0x48,0x33,0xFC,0x04,0x80,0xFF,0xFC,
+0x10,0x26,0x33,0xFC,0x01,0x10,0xFF,0xFC,0x10,0x2A,0x23,0xFC,0x00,0xD4,0x9F,0x40,
+0xFF,0xFC,0x15,0x40,0x23,0xFC,0x00,0x00,0x05,0x43,0xFF,0xF9,0x01,0x00,0x23,0xFC,
+0x00,0x00,0x05,0x43,0xFF,0xF9,0x01,0x14,0x23,0xFC,0x00,0x00,0x00,0x00,0xFF,0xF9,
+0x01,0x10,0x23,0xFC,0x00,0x00,0x00,0x08,0xFF,0xF9,0x01,0x24,0x23,0xFC,0x00,0x00,
+0x01,0x01,0xFF,0xF9,0x01,0x28,0x00,0xB9,0x00,0x0F,0x03,0x00,0xFF,0xF9,0x00,0xE8,
+0x23,0xFC,0x00,0x00,0x00,0x01,0xFF,0xF9,0x00,0xD4,0x61,0x00,0x06,0x74,0x33,0xFC,
+0xFF,0xFF,0xFF,0xFC,0x15,0x52,0x42,0x79,0xFF,0xFC,0x15,0x50,0x42,0x79,0xFF,0xFC,
+0x15,0x64,0x2E,0x3A,0x08,0x50,0x42,0xB9,0x00,0x00,0x19,0x54,0x4A,0x87,0x66,0x00,
+0x00,0x0E,0x4E,0x72,0x22,0x00,0x46,0xFC,0x27,0x00,0x60,0x00,0xFF,0xE6,0x42,0x80,
+0x42,0x86,0x08,0x07,0x00,0x04,0x67,0x00,0x00,0x0A,0x08,0x87,0x00,0x00,0x61,0x00,
+0x02,0xA0,0x08,0x07,0x00,0x00,0x67,0x00,0x00,0x06,0x61,0x00,0x00,0x36,0x08,0x07,
+0x00,0x08,0x67,0x00,0x00,0x06,0x61,0x00,0x02,0xB8,0x08,0x07,0x00,0x0C,0x67,0x00,
+0x00,0x0A,0x61,0x00,0x04,0x94,0x61,0x00,0x03,0x60,0xE2,0x8F,0x58,0x80,0x0C,0x80,
+0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0xBC,0x23,0xC6,0xFF,0xF9,0x00,0xE4,0x60,0x00,
+0xFF,0x92,0x20,0x70,0x09,0xB0,0x00,0x00,0x10,0x04,0x4A,0xA8,0x00,0x00,0x66,0x00,
+0x02,0x4E,0x21,0x7C,0x00,0x00,0x00,0x01,0x00,0x00,0x42,0xB0,0x09,0xB0,0x00,0x00,
+0x19,0x58,0x42,0xB0,0x09,0xB0,0x00,0x00,0x19,0x68,0x42,0xB0,0x09,0xB0,0x00,0x00,
+0x19,0x78,0x42,0xB0,0x09,0xB0,0x00,0x00,0x19,0x88,0x22,0x39,0xFF,0xFC,0x16,0xEC,
+0xC2,0xB0,0x09,0xB0,0x00,0x00,0x18,0xF2,0x0C,0xA8,0x00,0x00,0x00,0x04,0x00,0x18,
+0x66,0x00,0x00,0x0E,0x82,0xB0,0x09,0xB0,0x00,0x00,0x18,0xE2,0x60,0x00,0x00,0x0A,
+0x82,0xB0,0x09,0xB0,0x00,0x00,0x18,0xD2,0x23,0xC1,0xFF,0xFC,0x16,0xEC,0x00,0x70,
+0x10,0x00,0x09,0xB0,0x00,0x00,0x19,0xAA,0x61,0x00,0x05,0x76,0x22,0x30,0x09,0xB0,
+0x00,0x00,0x18,0x92,0x22,0x70,0x09,0xB0,0x00,0x00,0x18,0x72,0x74,0x08,0x26,0x3C,
+0x18,0x00,0x00,0x00,0x0C,0xA8,0x00,0x00,0x00,0x01,0x00,0x10,0x67,0x00,0x00,0x06,
+0x08,0xC3,0x00,0x1A,0x22,0xC3,0x22,0xC1,0x06,0x81,0x00,0x00,0x05,0xFC,0x51,0xCA,
+0xFF,0xF4,0x08,0xC3,0x00,0x1D,0x22,0xC3,0x22,0xC1,0x74,0x1C,0x22,0xFC,0x90,0x00,
+0x00,0x00,0x22,0xC1,0x06,0x81,0x00,0x00,0x05,0xFC,0x51,0xCA,0xFF,0xF0,0x22,0xFC,
+0xB0,0x00,0x00,0x00,0x22,0xC1,0x22,0x70,0x09,0xB0,0x00,0x00,0x18,0x62,0x24,0x70,
+0x09,0xB0,0x00,0x00,0x18,0x52,0x25,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x10,0x25,0x7C,
+0x00,0x00,0x00,0x00,0x00,0x14,0x22,0x30,0x09,0xB0,0x00,0x00,0x18,0x72,0x33,0x41,
+0x00,0x02,0x06,0x81,0x00,0x00,0x00,0x50,0x33,0x41,0x00,0x00,0x13,0x7C,0x00,0x08,
+0x00,0x04,0x13,0x7C,0x00,0x08,0x00,0x05,0x0C,0xA8,0x00,0x00,0x00,0x05,0x00,0x10,
+0x66,0x00,0x00,0x2A,0x42,0x6A,0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,
+0x23,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x38,0x33,0x7C,0x05,0xFA,0x00,0x46,0x31,0xBC,
+0x00,0x02,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,0x00,0xBC,0x0C,0xA8,0x00,0x00,
+0x00,0x07,0x00,0x10,0x66,0x00,0x00,0x2C,0x35,0x7C,0x08,0x00,0x00,0x08,0x23,0x7C,
+0xDE,0xBB,0x20,0xE3,0x00,0x34,0x23,0x7C,0xFF,0xFF,0xFF,0xFF,0x00,0x38,0x33,0x7C,
+0x05,0xFC,0x00,0x46,0x31,0xBC,0x00,0x04,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,
+0x00,0x86,0x0C,0xA8,0x00,0x00,0x00,0x04,0x00,0x10,0x66,0x00,0x00,0x26,0x42,0x6A,
+0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,0x42,0xA9,0x00,0x38,0x33,0x7C,
+0x05,0xFA,0x00,0x46,0x31,0xBC,0x00,0x02,0x09,0xB0,0x00,0x00,0x19,0x9C,0x60,0x00,
+0x00,0x56,0x0C,0xA8,0x00,0x00,0x00,0x06,0x00,0x10,0x66,0x00,0x00,0x28,0x35,0x7C,
+0x08,0x00,0x00,0x08,0x23,0x7C,0xDE,0xBB,0x20,0xE3,0x00,0x34,0x42,0xA9,0x00,0x38,
+0x33,0x7C,0x05,0xFC,0x00,0x46,0x31,0xBC,0x00,0x04,0x09,0xB0,0x00,0x00,0x19,0x9C,
+0x60,0x00,0x00,0x24,0x42,0x6A,0x00,0x08,0x23,0x7C,0x00,0x00,0xF0,0xB8,0x00,0x34,
+0x23,0x7C,0x00,0x00,0xFF,0xFF,0x00,0x38,0x33,0x7C,0x05,0xF8,0x00,0x46,0x42,0x70,
+0x09,0xB0,0x00,0x00,0x19,0x9C,0x25,0x7C,0x00,0x00,0x00,0x03,0x00,0x04,0x0C,0xA8,
+0x00,0x00,0x00,0x02,0x00,0x14,0x66,0x00,0x00,0x0E,0x25,0x7C,0x10,0x04,0x09,0x00,
+0x00,0x00,0x60,0x00,0x00,0x0A,0x25,0x7C,0x10,0x04,0x00,0x00,0x00,0x00,0x33,0x7C,
+0x05,0xFC,0x00,0x06,0x22,0x00,0xE9,0x89,0x00,0x81,0x00,0x00,0x00,0x01,0x33,0xC1,
+0xFF,0xFC,0x15,0xC0,0x08,0x39,0x00,0x00,0xFF,0xFC,0x15,0xC0,0x66,0x00,0xFF,0xF6,
+0x35,0x7C,0x00,0x1F,0x00,0x14,0x00,0xAA,0x00,0x00,0x00,0x30,0x00,0x00,0x4E,0x75,
+0x20,0x70,0x09,0xB0,0x00,0x00,0x18,0x52,0x42,0x68,0x00,0x14,0x02,0xA8,0xFF,0xFF,
+0xFF,0xCF,0x00,0x00,0x02,0x70,0xEF,0xFF,0x09,0xB0,0x00,0x00,0x19,0xAA,0x61,0x00,
+0x03,0x70,0x22,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x42,0xB0,0x19,0x90,0x4E,0x75,
+0x0C,0xB0,0x00,0x00,0x00,0x0A,0x09,0xB0,0x00,0x00,0x19,0x78,0x67,0x00,0x00,0xA8,
+0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x68,0x24,0x01,0x4C,0x3C,0x20,0x00,0x00,0x00,
+0x00,0x0C,0xD4,0xB0,0x09,0xB0,0x00,0x00,0x10,0x04,0x06,0x82,0x00,0x00,0x00,0x1C,
+0x0C,0xB0,0x00,0x00,0x00,0x10,0x29,0x90,0x66,0x00,0x00,0x7C,0x20,0x70,0x29,0xA0,
+0x00,0x04,0xE7,0x89,0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x72,0x22,0x70,0x19,0xA0,
+0x00,0x04,0x24,0x30,0x29,0xA0,0x00,0x08,0x31,0x82,0x19,0xA0,0x00,0x02,0x56,0x82,
+0x02,0x82,0xFF,0xFF,0xFF,0xFC,0x23,0xC8,0xFF,0xF9,0x01,0x04,0x23,0xC9,0xFF,0xF9,
+0x01,0x08,0x23,0xC2,0xFF,0xF9,0x01,0x0C,0x23,0xFC,0x00,0x00,0x01,0x03,0xFF,0xF9,
+0x01,0x28,0x61,0x00,0x01,0xF6,0x08,0xF0,0x00,0x1F,0x19,0x90,0x22,0x30,0x09,0xB0,
+0x00,0x00,0x19,0x68,0x52,0x81,0x0C,0x81,0x00,0x00,0x00,0x0A,0x66,0x00,0x00,0x04,
+0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,0x19,0x68,0x52,0xB0,0x09,0xB0,0x00,0x00,
+0x19,0x78,0x60,0x00,0xFF,0x4C,0x4E,0x75,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x88,
+0xE7,0x89,0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x82,0x34,0x30,0x19,0x90,0x08,0x02,
+0x00,0x0F,0x66,0x00,0x01,0x12,0x08,0x02,0x00,0x01,0x66,0x00,0x00,0xE6,0x4A,0x70,
+0x09,0xB0,0x00,0x00,0x19,0x9C,0x66,0x00,0x00,0x06,0x08,0x82,0x00,0x02,0x02,0x42,
+0x0C,0xBC,0x0C,0x42,0x0C,0x00,0x66,0x00,0x00,0xDC,0x42,0x83,0x36,0x30,0x19,0xA0,
+0x00,0x02,0x96,0x70,0x09,0xB0,0x00,0x00,0x19,0x9C,0x0C,0x43,0x05,0xF8,0x6E,0x00,
+0x00,0xC4,0x24,0x3A,0x04,0x84,0x4C,0x3C,0x20,0x00,0x00,0x00,0x00,0x0C,0xD4,0xBA,
+0xFA,0xF4,0x0C,0xB0,0x00,0x00,0x00,0x00,0x29,0x90,0x66,0x00,0x00,0x96,0x21,0x83,
+0x29,0xA0,0x00,0x08,0x20,0x70,0x19,0xA0,0x00,0x04,0x22,0x70,0x29,0xA0,0x00,0x04,
+0x4A,0x89,0x67,0x00,0x00,0x2A,0x56,0x83,0x02,0x83,0xFF,0xFF,0xFF,0xFC,0x23,0xC8,
+0xFF,0xF9,0x01,0x1C,0x23,0xC9,0xFF,0xF9,0x01,0x18,0x23,0xC3,0xFF,0xF9,0x01,0x20,
+0x23,0xFC,0x00,0x00,0x03,0x01,0xFF,0xF9,0x01,0x28,0x61,0x00,0x01,0x2C,0x21,0xB0,
+0x09,0xB0,0x00,0x00,0x18,0xC2,0x29,0x90,0x08,0xC6,0x00,0x04,0x24,0x3A,0x04,0x1A,
+0x52,0x82,0x0C,0x82,0x00,0x00,0x00,0x28,0x66,0x00,0x00,0x04,0x42,0x82,0x23,0xC2,
+0x00,0x00,0x19,0x98,0x02,0x70,0xF0,0x00,0x19,0x90,0x08,0xF0,0x00,0x1F,0x19,0x90,
+0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x88,0x52,0x81,0x0C,0x81,0x00,0x00,0x00,0x1E,
+0x66,0x00,0x00,0x04,0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,0x19,0x88,0x60,0x00,
+0xFE,0xF8,0x24,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x52,0xB0,0x29,0xA0,0x00,0x08,
+0x60,0x00,0xFF,0xC2,0x24,0x30,0x09,0xB0,0x00,0x00,0x10,0x04,0x52,0xB0,0x29,0xA0,
+0x00,0x0C,0x60,0x00,0xFF,0xB0,0x4E,0x75,0x4A,0xB0,0x09,0xB0,0x00,0x00,0x19,0x78,
+0x67,0x00,0x00,0x86,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x58,0x24,0x01,0xE7,0x89,
+0xD2,0xB0,0x09,0xB0,0x00,0x00,0x18,0x72,0x36,0x30,0x19,0x90,0x08,0x03,0x00,0x0F,
+0x66,0x00,0x00,0x66,0x8C,0xB0,0x09,0xB0,0x00,0x00,0x18,0xA2,0x53,0xB0,0x09,0xB0,
+0x00,0x00,0x19,0x78,0x22,0x30,0x09,0xB0,0x00,0x00,0x19,0x58,0x52,0x81,0x0C,0x81,
+0x00,0x00,0x00,0x0A,0x66,0x00,0x00,0x04,0x42,0x81,0x21,0x81,0x09,0xB0,0x00,0x00,
+0x19,0x58,0x4C,0x3C,0x20,0x00,0x00,0x00,0x00,0x0C,0xD4,0xB0,0x09,0xB0,0x00,0x00,
+0x10,0x04,0x06,0x82,0x00,0x00,0x00,0x1C,0x08,0x03,0x00,0x01,0x66,0x00,0x00,0x0E,
+0x21,0xBC,0x00,0x00,0x00,0x20,0x29,0x90,0x60,0x00,0xFF,0x7E,0x21,0xBC,0x00,0x00,
+0x00,0x30,0x29,0x90,0x60,0x00,0xFF,0x72,0x4E,0x75,0x2F,0x00,0x40,0xE7,0x20,0x39,
+0xFF,0xF9,0x01,0x28,0x08,0x00,0x00,0x04,0x66,0x00,0x00,0x2C,0x4E,0x72,0x22,0x00,
+0x46,0xFC,0x27,0x00,0x60,0x00,0xFF,0xE8,0x2F,0x00,0x40,0xE7,0x20,0x39,0xFF,0xF9,
+0x01,0x28,0x08,0x00,0x00,0x0C,0x66,0x00,0x00,0x0E,0x4E,0x72,0x22,0x00,0x46,0xFC,
+0x27,0x00,0x60,0x00,0xFF,0xE8,0x46,0xDF,0x20,0x1F,0x4E,0x75,0x2F,0x00,0x20,0x39,
+0xFF,0xF9,0x00,0xE0,0x23,0xC0,0xFF,0xF9,0x00,0xE0,0x81,0xB9,0x00,0x00,0x19,0x54,
+0x23,0xFC,0x00,0x00,0x09,0x09,0xFF,0xF9,0x01,0x28,0x20,0x1F,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x10,0x00,0xB9,0x00,0x00,0x10,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x40,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x30,0x00,0xB9,0x00,0x00,0x20,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x20,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x50,0x00,0xB9,0x00,0x00,0x40,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x10,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x00,0xB9,
+0x00,0x00,0x00,0x00,0xFF,0xFC,0x16,0x70,0x00,0xB9,0x00,0x00,0x80,0x00,0x00,0x00,
+0x19,0x54,0x23,0xFC,0x08,0x00,0x00,0x00,0xFF,0xFC,0x15,0x4C,0x4E,0x73,0x4E,0x73,
+0x2F,0x00,0x2F,0x01,0x2F,0x02,0x2F,0x08,0x2F,0x09,0x42,0x80,0x20,0x7C,0xFF,0xFB,
+0x00,0x00,0x32,0x10,0x02,0x81,0x00,0x00,0x00,0xE7,0x0C,0x41,0x00,0x42,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x0E,0x08,0x60,0x00,0x00,0x3E,0x0C,0x41,0x00,0x63,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x04,0x08,0x60,0x00,0x00,0x2E,0x0C,0x41,0x00,0x84,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x02,0x08,0x60,0x00,0x00,0x1E,0x0C,0x41,0x00,0xA5,0x66,0x00,
+0x00,0x0A,0x32,0x3C,0x0D,0x08,0x60,0x00,0x00,0x0E,0x32,0x3C,0x00,0x08,0x34,0x3C,
+0x80,0xE7,0x60,0x00,0x00,0x14,0x34,0x30,0x09,0xB0,0x00,0x00,0x19,0xAA,0x02,0x42,
+0x30,0x00,0x82,0x42,0x34,0x3C,0x80,0xFF,0xB2,0x70,0x09,0xB0,0x00,0x00,0x19,0xAC,
+0x67,0x00,0x00,0x0C,0x31,0x81,0x09,0xB0,0x00,0x00,0x19,0xAC,0x30,0x81,0x32,0x39,
+0xFF,0xFC,0x15,0x66,0xC2,0x70,0x09,0xB0,0x00,0x00,0x19,0x02,0x67,0x00,0x00,0x0C,
+0x32,0x10,0x02,0x41,0xFF,0xF7,0x60,0x00,0x00,0x08,0x32,0x10,0x00,0x41,0x00,0x08,
+0xC2,0x42,0x22,0x70,0x09,0xB0,0x00,0x00,0x10,0x04,0xB2,0xA9,0x00,0x04,0x67,0x00,
+0x00,0x12,0x23,0x41,0x00,0x04,0x23,0xF0,0x09,0xB0,0x00,0x00,0x18,0xB2,0xFF,0xF9,
+0x00,0xE4,0x54,0x88,0x58,0x80,0x0C,0x80,0x00,0x00,0x00,0x10,0x66,0x00,0xFF,0x34,
+0x22,0x5F,0x20,0x5F,0x24,0x1F,0x22,0x1F,0x20,0x1F,0x4E,0x75,0x61,0x00,0xFF,0x12,
+0x4E,0x73,0xFF,0xFC,0x16,0x00,0xFF,0xFC,0x16,0x20,0xFF,0xFC,0x16,0x40,0xFF,0xFC,
+0x16,0x60,0xFF,0xFC,0x0C,0x00,0xFF,0xFC,0x0D,0x00,0xFF,0xFC,0x0E,0x00,0xFF,0xFC,
+0x0F,0x00,0xFF,0xFC,0x00,0x00,0xFF,0xFC,0x01,0x40,0xFF,0xFC,0x02,0x80,0xFF,0xFC,
+0x03,0xC0,0xFF,0xFC,0x00,0x50,0xFF,0xFC,0x01,0x90,0xFF,0xFC,0x02,0xD0,0xFF,0xFC,
+0x04,0x10,0x00,0x00,0x40,0x00,0x00,0x01,0x2F,0x60,0x00,0x02,0x1E,0xC0,0x00,0x03,
+0x0E,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,
+0x00,0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x80,0x00,0x00,
+0x01,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x12,0x00,0x00,
+0x00,0x13,0x00,0x00,0x00,0x2C,0x00,0x00,0x3E,0x00,0x00,0x2C,0x00,0x00,0x3E,0x00,
+0x00,0x00,0x00,0x00,0x00,0x2D,0x00,0x00,0x3F,0x00,0x00,0x2D,0x00,0x00,0x3F,0x00,
+0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,
+0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x80,0x00,0x00,0x02,0x00,0x00,0x00,0x08,0x00,
+0x77,0x61,0x6E,0x58,0x4C,0x20,0x66,0x69,0x72,0x6D,0x77,0x61,0x72,0x65,0x0A,0x43,
+0x6F,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x43,0x29,0x20,0x32,0x30,0x30,
+0x33,0x20,0x4B,0x72,0x7A,0x79,0x73,0x7A,0x74,0x6F,0x66,0x20,0x48,0x61,0x6C,0x61,
+0x73,0x61,0x20,0x3C,0x6B,0x68,0x63,0x40,0x70,0x6D,0x2E,0x77,0x61,0x77,0x2E,0x70,
+0x6C,0x3E,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+};
diff --git a/drivers/net/wan/x25_asy.c b/drivers/net/wan/x25_asy.c
new file mode 100644
index 00000000000..8c5cfcb5582
--- /dev/null
+++ b/drivers/net/wan/x25_asy.c
@@ -0,0 +1,844 @@
+/*
+ * Things to sort out:
+ *
+ * o tbusy handling
+ * o allow users to set the parameters
+ * o sync/async switching ?
+ *
+ * Note: This does _not_ implement CCITT X.25 asynchronous framing
+ * recommendations. Its primarily for testing purposes. If you wanted
+ * to do CCITT then in theory all you need is to nick the HDLC async
+ * checksum routines from ppp.c
+ * Changes:
+ *
+ * 2000-10-29 Henner Eisen lapb_data_indication() return status.
+ */
+
+#include <linux/module.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/x25.h>
+#include <linux/lapb.h>
+#include <linux/init.h>
+#include "x25_asy.h"
+
+#include <net/x25device.h>
+
+static struct net_device **x25_asy_devs;
+static int x25_asy_maxdev = SL_NRUNIT;
+
+module_param(x25_asy_maxdev, int, 0);
+MODULE_LICENSE("GPL");
+
+static int x25_asy_esc(unsigned char *p, unsigned char *d, int len);
+static void x25_asy_unesc(struct x25_asy *sl, unsigned char c);
+static void x25_asy_setup(struct net_device *dev);
+
+/* Find a free X.25 channel, and link in this `tty' line. */
+static struct x25_asy *x25_asy_alloc(void)
+{
+ struct net_device *dev = NULL;
+ struct x25_asy *sl;
+ int i;
+
+ if (x25_asy_devs == NULL)
+ return NULL; /* Master array missing ! */
+
+ for (i = 0; i < x25_asy_maxdev; i++) {
+ dev = x25_asy_devs[i];
+
+ /* Not allocated ? */
+ if (dev == NULL)
+ break;
+
+ sl = dev->priv;
+ /* Not in use ? */
+ if (!test_and_set_bit(SLF_INUSE, &sl->flags))
+ return sl;
+ }
+
+
+ /* Sorry, too many, all slots in use */
+ if (i >= x25_asy_maxdev)
+ return NULL;
+
+ /* If no channels are available, allocate one */
+ if (!dev) {
+ char name[IFNAMSIZ];
+ sprintf(name, "x25asy%d", i);
+
+ dev = alloc_netdev(sizeof(struct x25_asy),
+ name, x25_asy_setup);
+ if (!dev)
+ return NULL;
+
+ /* Initialize channel control data */
+ sl = dev->priv;
+ dev->base_addr = i;
+
+ /* register device so that it can be ifconfig'ed */
+ if (register_netdev(dev) == 0) {
+ /* (Re-)Set the INUSE bit. Very Important! */
+ set_bit(SLF_INUSE, &sl->flags);
+ x25_asy_devs[i] = dev;
+ return sl;
+ } else {
+ printk("x25_asy_alloc() - register_netdev() failure.\n");
+ free_netdev(dev);
+ }
+ }
+ return NULL;
+}
+
+
+/* Free an X.25 channel. */
+static void x25_asy_free(struct x25_asy *sl)
+{
+ /* Free all X.25 frame buffers. */
+ if (sl->rbuff) {
+ kfree(sl->rbuff);
+ }
+ sl->rbuff = NULL;
+ if (sl->xbuff) {
+ kfree(sl->xbuff);
+ }
+ sl->xbuff = NULL;
+
+ if (!test_and_clear_bit(SLF_INUSE, &sl->flags)) {
+ printk("%s: x25_asy_free for already free unit.\n", sl->dev->name);
+ }
+}
+
+static int x25_asy_change_mtu(struct net_device *dev, int newmtu)
+{
+ struct x25_asy *sl = dev->priv;
+ unsigned char *xbuff, *rbuff;
+ int len = 2* newmtu;
+
+ xbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+ rbuff = (unsigned char *) kmalloc (len + 4, GFP_ATOMIC);
+
+ if (xbuff == NULL || rbuff == NULL)
+ {
+ printk("%s: unable to grow X.25 buffers, MTU change cancelled.\n",
+ dev->name);
+ if (xbuff != NULL)
+ kfree(xbuff);
+ if (rbuff != NULL)
+ kfree(rbuff);
+ return -ENOMEM;
+ }
+
+ spin_lock_bh(&sl->lock);
+ xbuff = xchg(&sl->xbuff, xbuff);
+ if (sl->xleft) {
+ if (sl->xleft <= len) {
+ memcpy(sl->xbuff, sl->xhead, sl->xleft);
+ } else {
+ sl->xleft = 0;
+ sl->stats.tx_dropped++;
+ }
+ }
+ sl->xhead = sl->xbuff;
+
+ rbuff = xchg(&sl->rbuff, rbuff);
+ if (sl->rcount) {
+ if (sl->rcount <= len) {
+ memcpy(sl->rbuff, rbuff, sl->rcount);
+ } else {
+ sl->rcount = 0;
+ sl->stats.rx_over_errors++;
+ set_bit(SLF_ERROR, &sl->flags);
+ }
+ }
+
+ dev->mtu = newmtu;
+ sl->buffsize = len;
+
+ spin_unlock_bh(&sl->lock);
+
+ if (xbuff != NULL)
+ kfree(xbuff);
+ if (rbuff != NULL)
+ kfree(rbuff);
+ return 0;
+}
+
+
+/* Set the "sending" flag. This must be atomic, hence the ASM. */
+
+static inline void x25_asy_lock(struct x25_asy *sl)
+{
+ netif_stop_queue(sl->dev);
+}
+
+
+/* Clear the "sending" flag. This must be atomic, hence the ASM. */
+
+static inline void x25_asy_unlock(struct x25_asy *sl)
+{
+ netif_wake_queue(sl->dev);
+}
+
+/* Send one completely decapsulated IP datagram to the IP layer. */
+
+static void x25_asy_bump(struct x25_asy *sl)
+{
+ struct sk_buff *skb;
+ int count;
+ int err;
+
+ count = sl->rcount;
+ sl->stats.rx_bytes+=count;
+
+ skb = dev_alloc_skb(count+1);
+ if (skb == NULL)
+ {
+ printk("%s: memory squeeze, dropping packet.\n", sl->dev->name);
+ sl->stats.rx_dropped++;
+ return;
+ }
+ skb_push(skb,1); /* LAPB internal control */
+ memcpy(skb_put(skb,count), sl->rbuff, count);
+ skb->protocol = x25_type_trans(skb, sl->dev);
+ if((err=lapb_data_received(skb->dev, skb))!=LAPB_OK)
+ {
+ kfree_skb(skb);
+ printk(KERN_DEBUG "x25_asy: data received err - %d\n",err);
+ }
+ else
+ {
+ netif_rx(skb);
+ sl->dev->last_rx = jiffies;
+ sl->stats.rx_packets++;
+ }
+}
+
+/* Encapsulate one IP datagram and stuff into a TTY queue. */
+static void x25_asy_encaps(struct x25_asy *sl, unsigned char *icp, int len)
+{
+ unsigned char *p;
+ int actual, count, mtu = sl->dev->mtu;
+
+ if (len > mtu)
+ { /* Sigh, shouldn't occur BUT ... */
+ len = mtu;
+ printk ("%s: truncating oversized transmit packet!\n", sl->dev->name);
+ sl->stats.tx_dropped++;
+ x25_asy_unlock(sl);
+ return;
+ }
+
+ p = icp;
+ count = x25_asy_esc(p, (unsigned char *) sl->xbuff, len);
+
+ /* Order of next two lines is *very* important.
+ * When we are sending a little amount of data,
+ * the transfer may be completed inside driver.write()
+ * routine, because it's running with interrupts enabled.
+ * In this case we *never* got WRITE_WAKEUP event,
+ * if we did not request it before write operation.
+ * 14 Oct 1994 Dmitry Gorodchanin.
+ */
+ sl->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
+ actual = sl->tty->driver->write(sl->tty, sl->xbuff, count);
+ sl->xleft = count - actual;
+ sl->xhead = sl->xbuff + actual;
+ /* VSV */
+ clear_bit(SLF_OUTWAIT, &sl->flags); /* reset outfill flag */
+}
+
+/*
+ * Called by the driver when there's room for more data. If we have
+ * more packets to send, we send them here.
+ */
+static void x25_asy_write_wakeup(struct tty_struct *tty)
+{
+ int actual;
+ struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+ /* First make sure we're connected. */
+ if (!sl || sl->magic != X25_ASY_MAGIC || !netif_running(sl->dev))
+ return;
+
+ if (sl->xleft <= 0)
+ {
+ /* Now serial buffer is almost free & we can start
+ * transmission of another packet */
+ sl->stats.tx_packets++;
+ tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+ x25_asy_unlock(sl);
+ return;
+ }
+
+ actual = tty->driver->write(tty, sl->xhead, sl->xleft);
+ sl->xleft -= actual;
+ sl->xhead += actual;
+}
+
+static void x25_asy_timeout(struct net_device *dev)
+{
+ struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+
+ spin_lock(&sl->lock);
+ if (netif_queue_stopped(dev)) {
+ /* May be we must check transmitter timeout here ?
+ * 14 Oct 1994 Dmitry Gorodchanin.
+ */
+ printk(KERN_WARNING "%s: transmit timed out, %s?\n", dev->name,
+ (sl->tty->driver->chars_in_buffer(sl->tty) || sl->xleft) ?
+ "bad line quality" : "driver error");
+ sl->xleft = 0;
+ sl->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+ x25_asy_unlock(sl);
+ }
+ spin_unlock(&sl->lock);
+}
+
+/* Encapsulate an IP datagram and kick it into a TTY queue. */
+
+static int x25_asy_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+ int err;
+
+ if (!netif_running(sl->dev)) {
+ printk("%s: xmit call when iface is down\n", dev->name);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ switch(skb->data[0])
+ {
+ case 0x00:break;
+ case 0x01: /* Connection request .. do nothing */
+ if((err=lapb_connect_request(dev))!=LAPB_OK)
+ printk(KERN_ERR "x25_asy: lapb_connect_request error - %d\n", err);
+ kfree_skb(skb);
+ return 0;
+ case 0x02: /* Disconnect request .. do nothing - hang up ?? */
+ if((err=lapb_disconnect_request(dev))!=LAPB_OK)
+ printk(KERN_ERR "x25_asy: lapb_disconnect_request error - %d\n", err);
+ default:
+ kfree_skb(skb);
+ return 0;
+ }
+ skb_pull(skb,1); /* Remove control byte */
+ /*
+ * If we are busy already- too bad. We ought to be able
+ * to queue things at this point, to allow for a little
+ * frame buffer. Oh well...
+ * -----------------------------------------------------
+ * I hate queues in X.25 driver. May be it's efficient,
+ * but for me latency is more important. ;)
+ * So, no queues !
+ * 14 Oct 1994 Dmitry Gorodchanin.
+ */
+
+ if((err=lapb_data_request(dev,skb))!=LAPB_OK)
+ {
+ printk(KERN_ERR "lapbeth: lapb_data_request error - %d\n", err);
+ kfree_skb(skb);
+ return 0;
+ }
+ return 0;
+}
+
+
+/*
+ * LAPB interface boilerplate
+ */
+
+/*
+ * Called when I frame data arrives. We did the work above - throw it
+ * at the net layer.
+ */
+
+static int x25_asy_data_indication(struct net_device *dev, struct sk_buff *skb)
+{
+ skb->dev->last_rx = jiffies;
+ return netif_rx(skb);
+}
+
+/*
+ * Data has emerged from the LAPB protocol machine. We don't handle
+ * busy cases too well. Its tricky to see how to do this nicely -
+ * perhaps lapb should allow us to bounce this ?
+ */
+
+static void x25_asy_data_transmit(struct net_device *dev, struct sk_buff *skb)
+{
+ struct x25_asy *sl=dev->priv;
+
+ spin_lock(&sl->lock);
+ if (netif_queue_stopped(sl->dev) || sl->tty == NULL)
+ {
+ spin_unlock(&sl->lock);
+ printk(KERN_ERR "x25_asy: tbusy drop\n");
+ kfree_skb(skb);
+ return;
+ }
+ /* We were not busy, so we are now... :-) */
+ if (skb != NULL)
+ {
+ x25_asy_lock(sl);
+ sl->stats.tx_bytes+=skb->len;
+ x25_asy_encaps(sl, skb->data, skb->len);
+ dev_kfree_skb(skb);
+ }
+ spin_unlock(&sl->lock);
+}
+
+/*
+ * LAPB connection establish/down information.
+ */
+
+static void x25_asy_connected(struct net_device *dev, int reason)
+{
+ struct x25_asy *sl = dev->priv;
+ struct sk_buff *skb;
+ unsigned char *ptr;
+
+ if ((skb = dev_alloc_skb(1)) == NULL) {
+ printk(KERN_ERR "lapbeth: out of memory\n");
+ return;
+ }
+
+ ptr = skb_put(skb, 1);
+ *ptr = 0x01;
+
+ skb->protocol = x25_type_trans(skb, sl->dev);
+ netif_rx(skb);
+ sl->dev->last_rx = jiffies;
+}
+
+static void x25_asy_disconnected(struct net_device *dev, int reason)
+{
+ struct x25_asy *sl = dev->priv;
+ struct sk_buff *skb;
+ unsigned char *ptr;
+
+ if ((skb = dev_alloc_skb(1)) == NULL) {
+ printk(KERN_ERR "x25_asy: out of memory\n");
+ return;
+ }
+
+ ptr = skb_put(skb, 1);
+ *ptr = 0x02;
+
+ skb->protocol = x25_type_trans(skb, sl->dev);
+ netif_rx(skb);
+ sl->dev->last_rx = jiffies;
+}
+
+static struct lapb_register_struct x25_asy_callbacks = {
+ .connect_confirmation = x25_asy_connected,
+ .connect_indication = x25_asy_connected,
+ .disconnect_confirmation = x25_asy_disconnected,
+ .disconnect_indication = x25_asy_disconnected,
+ .data_indication = x25_asy_data_indication,
+ .data_transmit = x25_asy_data_transmit,
+
+};
+
+
+/* Open the low-level part of the X.25 channel. Easy! */
+static int x25_asy_open(struct net_device *dev)
+{
+ struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+ unsigned long len;
+ int err;
+
+ if (sl->tty == NULL)
+ return -ENODEV;
+
+ /*
+ * Allocate the X.25 frame buffers:
+ *
+ * rbuff Receive buffer.
+ * xbuff Transmit buffer.
+ */
+
+ len = dev->mtu * 2;
+
+ sl->rbuff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+ if (sl->rbuff == NULL) {
+ goto norbuff;
+ }
+ sl->xbuff = (unsigned char *) kmalloc(len + 4, GFP_KERNEL);
+ if (sl->xbuff == NULL) {
+ goto noxbuff;
+ }
+
+ sl->buffsize = len;
+ sl->rcount = 0;
+ sl->xleft = 0;
+ sl->flags &= (1 << SLF_INUSE); /* Clear ESCAPE & ERROR flags */
+
+ netif_start_queue(dev);
+
+ /*
+ * Now attach LAPB
+ */
+ if((err=lapb_register(dev, &x25_asy_callbacks))==LAPB_OK)
+ return 0;
+
+ /* Cleanup */
+ kfree(sl->xbuff);
+noxbuff:
+ kfree(sl->rbuff);
+norbuff:
+ return -ENOMEM;
+}
+
+
+/* Close the low-level part of the X.25 channel. Easy! */
+static int x25_asy_close(struct net_device *dev)
+{
+ struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+ int err;
+
+ spin_lock(&sl->lock);
+ if (sl->tty)
+ sl->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+
+ netif_stop_queue(dev);
+ sl->rcount = 0;
+ sl->xleft = 0;
+ if((err=lapb_unregister(dev))!=LAPB_OK)
+ printk(KERN_ERR "x25_asy_close: lapb_unregister error -%d\n",err);
+ spin_unlock(&sl->lock);
+ return 0;
+}
+
+static int x25_asy_receive_room(struct tty_struct *tty)
+{
+ return 65536; /* We can handle an infinite amount of data. :-) */
+}
+
+/*
+ * Handle the 'receiver data ready' interrupt.
+ * This function is called by the 'tty_io' module in the kernel when
+ * a block of X.25 data has been received, which can now be decapsulated
+ * and sent on to some IP layer for further processing.
+ */
+
+static void x25_asy_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
+{
+ struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+ if (!sl || sl->magic != X25_ASY_MAGIC || !netif_running(sl->dev))
+ return;
+
+
+ /* Read the characters out of the buffer */
+ while (count--) {
+ if (fp && *fp++) {
+ if (!test_and_set_bit(SLF_ERROR, &sl->flags)) {
+ sl->stats.rx_errors++;
+ }
+ cp++;
+ continue;
+ }
+ x25_asy_unesc(sl, *cp++);
+ }
+}
+
+/*
+ * Open the high-level part of the X.25 channel.
+ * This function is called by the TTY module when the
+ * X.25 line discipline is called for. Because we are
+ * sure the tty line exists, we only have to link it to
+ * a free X.25 channel...
+ */
+
+static int x25_asy_open_tty(struct tty_struct *tty)
+{
+ struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+ int err;
+
+ /* First make sure we're not already connected. */
+ if (sl && sl->magic == X25_ASY_MAGIC) {
+ return -EEXIST;
+ }
+
+ /* OK. Find a free X.25 channel to use. */
+ if ((sl = x25_asy_alloc()) == NULL) {
+ return -ENFILE;
+ }
+
+ sl->tty = tty;
+ tty->disc_data = sl;
+ if (tty->driver->flush_buffer) {
+ tty->driver->flush_buffer(tty);
+ }
+ if (tty->ldisc.flush_buffer) {
+ tty->ldisc.flush_buffer(tty);
+ }
+
+ /* Restore default settings */
+ sl->dev->type = ARPHRD_X25;
+
+ /* Perform the low-level X.25 async init */
+ if ((err = x25_asy_open(sl->dev)))
+ return err;
+
+ /* Done. We have linked the TTY line to a channel. */
+ return sl->dev->base_addr;
+}
+
+
+/*
+ * Close down an X.25 channel.
+ * This means flushing out any pending queues, and then restoring the
+ * TTY line discipline to what it was before it got hooked to X.25
+ * (which usually is TTY again).
+ */
+static void x25_asy_close_tty(struct tty_struct *tty)
+{
+ struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+ /* First make sure we're connected. */
+ if (!sl || sl->magic != X25_ASY_MAGIC)
+ return;
+
+ if (sl->dev->flags & IFF_UP)
+ {
+ (void) dev_close(sl->dev);
+ }
+
+ tty->disc_data = NULL;
+ sl->tty = NULL;
+ x25_asy_free(sl);
+}
+
+
+static struct net_device_stats *x25_asy_get_stats(struct net_device *dev)
+{
+ struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+
+ return &sl->stats;
+}
+
+
+ /************************************************************************
+ * STANDARD X.25 ENCAPSULATION *
+ ************************************************************************/
+
+int x25_asy_esc(unsigned char *s, unsigned char *d, int len)
+{
+ unsigned char *ptr = d;
+ unsigned char c;
+
+ /*
+ * Send an initial END character to flush out any
+ * data that may have accumulated in the receiver
+ * due to line noise.
+ */
+
+ *ptr++ = X25_END; /* Send 10111110 bit seq */
+
+ /*
+ * For each byte in the packet, send the appropriate
+ * character sequence, according to the X.25 protocol.
+ */
+
+ while (len-- > 0)
+ {
+ switch(c = *s++)
+ {
+ case X25_END:
+ *ptr++ = X25_ESC;
+ *ptr++ = X25_ESCAPE(X25_END);
+ break;
+ case X25_ESC:
+ *ptr++ = X25_ESC;
+ *ptr++ = X25_ESCAPE(X25_ESC);
+ break;
+ default:
+ *ptr++ = c;
+ break;
+ }
+ }
+ *ptr++ = X25_END;
+ return (ptr - d);
+}
+
+static void x25_asy_unesc(struct x25_asy *sl, unsigned char s)
+{
+
+ switch(s)
+ {
+ case X25_END:
+ if (!test_and_clear_bit(SLF_ERROR, &sl->flags) && (sl->rcount > 2))
+ {
+ x25_asy_bump(sl);
+ }
+ clear_bit(SLF_ESCAPE, &sl->flags);
+ sl->rcount = 0;
+ return;
+
+ case X25_ESC:
+ set_bit(SLF_ESCAPE, &sl->flags);
+ return;
+
+ case X25_ESCAPE(X25_ESC):
+ case X25_ESCAPE(X25_END):
+ if (test_and_clear_bit(SLF_ESCAPE, &sl->flags))
+ s = X25_UNESCAPE(s);
+ break;
+ }
+ if (!test_bit(SLF_ERROR, &sl->flags))
+ {
+ if (sl->rcount < sl->buffsize)
+ {
+ sl->rbuff[sl->rcount++] = s;
+ return;
+ }
+ sl->stats.rx_over_errors++;
+ set_bit(SLF_ERROR, &sl->flags);
+ }
+}
+
+
+/* Perform I/O control on an active X.25 channel. */
+static int x25_asy_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct x25_asy *sl = (struct x25_asy *) tty->disc_data;
+
+ /* First make sure we're connected. */
+ if (!sl || sl->magic != X25_ASY_MAGIC)
+ return -EINVAL;
+
+ switch(cmd) {
+ case SIOCGIFNAME:
+ if (copy_to_user((void __user *)arg, sl->dev->name,
+ strlen(sl->dev->name) + 1))
+ return -EFAULT;
+ return 0;
+ case SIOCSIFHWADDR:
+ return -EINVAL;
+ /* Allow stty to read, but not set, the serial port */
+ case TCGETS:
+ case TCGETA:
+ return n_tty_ioctl(tty, file, cmd, arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int x25_asy_open_dev(struct net_device *dev)
+{
+ struct x25_asy *sl = (struct x25_asy*)(dev->priv);
+ if(sl->tty==NULL)
+ return -ENODEV;
+ return 0;
+}
+
+/* Initialise the X.25 driver. Called by the device init code */
+static void x25_asy_setup(struct net_device *dev)
+{
+ struct x25_asy *sl = dev->priv;
+
+ sl->magic = X25_ASY_MAGIC;
+ sl->dev = dev;
+ spin_lock_init(&sl->lock);
+ set_bit(SLF_INUSE, &sl->flags);
+
+ /*
+ * Finish setting up the DEVICE info.
+ */
+
+ dev->mtu = SL_MTU;
+ dev->hard_start_xmit = x25_asy_xmit;
+ dev->tx_timeout = x25_asy_timeout;
+ dev->watchdog_timeo = HZ*20;
+ dev->open = x25_asy_open_dev;
+ dev->stop = x25_asy_close;
+ dev->get_stats = x25_asy_get_stats;
+ dev->change_mtu = x25_asy_change_mtu;
+ dev->hard_header_len = 0;
+ dev->addr_len = 0;
+ dev->type = ARPHRD_X25;
+ dev->tx_queue_len = 10;
+
+ /* New-style flags. */
+ dev->flags = IFF_NOARP;
+}
+
+static struct tty_ldisc x25_ldisc = {
+ .owner = THIS_MODULE,
+ .magic = TTY_LDISC_MAGIC,
+ .name = "X.25",
+ .open = x25_asy_open_tty,
+ .close = x25_asy_close_tty,
+ .ioctl = x25_asy_ioctl,
+ .receive_buf = x25_asy_receive_buf,
+ .receive_room = x25_asy_receive_room,
+ .write_wakeup = x25_asy_write_wakeup,
+};
+
+static int __init init_x25_asy(void)
+{
+ if (x25_asy_maxdev < 4)
+ x25_asy_maxdev = 4; /* Sanity */
+
+ printk(KERN_INFO "X.25 async: version 0.00 ALPHA "
+ "(dynamic channels, max=%d).\n", x25_asy_maxdev );
+
+ x25_asy_devs = kmalloc(sizeof(struct net_device *)*x25_asy_maxdev,
+ GFP_KERNEL);
+ if (!x25_asy_devs) {
+ printk(KERN_WARNING "X25 async: Can't allocate x25_asy_ctrls[] "
+ "array! Uaargh! (-> No X.25 available)\n");
+ return -ENOMEM;
+ }
+ memset(x25_asy_devs, 0, sizeof(struct net_device *)*x25_asy_maxdev);
+
+ return tty_register_ldisc(N_X25, &x25_ldisc);
+}
+
+
+static void __exit exit_x25_asy(void)
+{
+ struct net_device *dev;
+ int i;
+
+ for (i = 0; i < x25_asy_maxdev; i++) {
+ dev = x25_asy_devs[i];
+ if (dev) {
+ struct x25_asy *sl = dev->priv;
+
+ spin_lock_bh(&sl->lock);
+ if (sl->tty)
+ tty_hangup(sl->tty);
+
+ spin_unlock_bh(&sl->lock);
+ /*
+ * VSV = if dev->start==0, then device
+ * unregistered while close proc.
+ */
+ unregister_netdev(dev);
+ free_netdev(dev);
+ }
+ }
+
+ kfree(x25_asy_devs);
+ tty_register_ldisc(N_X25, NULL);
+}
+
+module_init(init_x25_asy);
+module_exit(exit_x25_asy);
diff --git a/drivers/net/wan/x25_asy.h b/drivers/net/wan/x25_asy.h
new file mode 100644
index 00000000000..41770200ceb
--- /dev/null
+++ b/drivers/net/wan/x25_asy.h
@@ -0,0 +1,50 @@
+#ifndef _LINUX_X25_ASY_H
+#define _LINUX_X25_ASY_H
+
+/* X.25 asy configuration. */
+#define SL_NRUNIT 256 /* MAX number of X.25 channels;
+ This can be overridden with
+ insmod -ox25_asy_maxdev=nnn */
+#define SL_MTU 256
+
+/* X25 async protocol characters. */
+#define X25_END 0x7E /* indicates end of frame */
+#define X25_ESC 0x7D /* indicates byte stuffing */
+#define X25_ESCAPE(x) ((x)^0x20)
+#define X25_UNESCAPE(x) ((x)^0x20)
+
+
+struct x25_asy {
+ int magic;
+
+ /* Various fields. */
+ spinlock_t lock;
+ struct tty_struct *tty; /* ptr to TTY structure */
+ struct net_device *dev; /* easy for intr handling */
+
+ /* These are pointers to the malloc()ed frame buffers. */
+ unsigned char *rbuff; /* receiver buffer */
+ int rcount; /* received chars counter */
+ unsigned char *xbuff; /* transmitter buffer */
+ unsigned char *xhead; /* pointer to next byte to XMIT */
+ int xleft; /* bytes left in XMIT queue */
+
+ /* X.25 interface statistics. */
+ struct net_device_stats stats;
+
+ int buffsize; /* Max buffers sizes */
+
+ unsigned long flags; /* Flag values/ mode etc */
+#define SLF_INUSE 0 /* Channel in use */
+#define SLF_ESCAPE 1 /* ESC received */
+#define SLF_ERROR 2 /* Parity, etc. error */
+#define SLF_OUTWAIT 4 /* Waiting for output */
+};
+
+
+
+#define X25_ASY_MAGIC 0x5303
+
+extern int x25_asy_init(struct net_device *dev);
+
+#endif /* _LINUX_X25_ASY.H */
diff --git a/drivers/net/wan/z85230.c b/drivers/net/wan/z85230.c
new file mode 100644
index 00000000000..caa48f12fd0
--- /dev/null
+++ b/drivers/net/wan/z85230.c
@@ -0,0 +1,1851 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * (c) Copyright 1998 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ * (c) Copyright 2000, 2001 Red Hat Inc
+ *
+ * Development of this driver was funded by Equiinet Ltd
+ * http://www.equiinet.com
+ *
+ * ChangeLog:
+ *
+ * Asynchronous mode dropped for 2.2. For 2.5 we will attempt the
+ * unification of all the Z85x30 asynchronous drivers for real.
+ *
+ * DMA now uses get_free_page as kmalloc buffers may span a 64K
+ * boundary.
+ *
+ * Modified for SMP safety and SMP locking by Alan Cox <alan@redhat.com>
+ *
+ * Performance
+ *
+ * Z85230:
+ * Non DMA you want a 486DX50 or better to do 64Kbits. 9600 baud
+ * X.25 is not unrealistic on all machines. DMA mode can in theory
+ * handle T1/E1 quite nicely. In practice the limit seems to be about
+ * 512Kbit->1Mbit depending on motherboard.
+ *
+ * Z85C30:
+ * 64K will take DMA, 9600 baud X.25 should be ok.
+ *
+ * Z8530:
+ * Synchronous mode without DMA is unlikely to pass about 2400 baud.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#define RT_LOCK
+#define RT_UNLOCK
+#include <linux/spinlock.h>
+
+#include <net/syncppp.h>
+#include "z85230.h"
+
+
+/**
+ * z8530_read_port - Architecture specific interface function
+ * @p: port to read
+ *
+ * Provided port access methods. The Comtrol SV11 requires no delays
+ * between accesses and uses PC I/O. Some drivers may need a 5uS delay
+ *
+ * In the longer term this should become an architecture specific
+ * section so that this can become a generic driver interface for all
+ * platforms. For now we only handle PC I/O ports with or without the
+ * dread 5uS sanity delay.
+ *
+ * The caller must hold sufficient locks to avoid violating the horrible
+ * 5uS delay rule.
+ */
+
+static inline int z8530_read_port(unsigned long p)
+{
+ u8 r=inb(Z8530_PORT_OF(p));
+ if(p&Z8530_PORT_SLEEP) /* gcc should figure this out efficiently ! */
+ udelay(5);
+ return r;
+}
+
+/**
+ * z8530_write_port - Architecture specific interface function
+ * @p: port to write
+ * @d: value to write
+ *
+ * Write a value to a port with delays if need be. Note that the
+ * caller must hold locks to avoid read/writes from other contexts
+ * violating the 5uS rule
+ *
+ * In the longer term this should become an architecture specific
+ * section so that this can become a generic driver interface for all
+ * platforms. For now we only handle PC I/O ports with or without the
+ * dread 5uS sanity delay.
+ */
+
+
+static inline void z8530_write_port(unsigned long p, u8 d)
+{
+ outb(d,Z8530_PORT_OF(p));
+ if(p&Z8530_PORT_SLEEP)
+ udelay(5);
+}
+
+
+
+static void z8530_rx_done(struct z8530_channel *c);
+static void z8530_tx_done(struct z8530_channel *c);
+
+
+/**
+ * read_zsreg - Read a register from a Z85230
+ * @c: Z8530 channel to read from (2 per chip)
+ * @reg: Register to read
+ * FIXME: Use a spinlock.
+ *
+ * Most of the Z8530 registers are indexed off the control registers.
+ * A read is done by writing to the control register and reading the
+ * register back. The caller must hold the lock
+ */
+
+static inline u8 read_zsreg(struct z8530_channel *c, u8 reg)
+{
+ if(reg)
+ z8530_write_port(c->ctrlio, reg);
+ return z8530_read_port(c->ctrlio);
+}
+
+/**
+ * read_zsdata - Read the data port of a Z8530 channel
+ * @c: The Z8530 channel to read the data port from
+ *
+ * The data port provides fast access to some things. We still
+ * have all the 5uS delays to worry about.
+ */
+
+static inline u8 read_zsdata(struct z8530_channel *c)
+{
+ u8 r;
+ r=z8530_read_port(c->dataio);
+ return r;
+}
+
+/**
+ * write_zsreg - Write to a Z8530 channel register
+ * @c: The Z8530 channel
+ * @reg: Register number
+ * @val: Value to write
+ *
+ * Write a value to an indexed register. The caller must hold the lock
+ * to honour the irritating delay rules. We know about register 0
+ * being fast to access.
+ *
+ * Assumes c->lock is held.
+ */
+static inline void write_zsreg(struct z8530_channel *c, u8 reg, u8 val)
+{
+ if(reg)
+ z8530_write_port(c->ctrlio, reg);
+ z8530_write_port(c->ctrlio, val);
+
+}
+
+/**
+ * write_zsctrl - Write to a Z8530 control register
+ * @c: The Z8530 channel
+ * @val: Value to write
+ *
+ * Write directly to the control register on the Z8530
+ */
+
+static inline void write_zsctrl(struct z8530_channel *c, u8 val)
+{
+ z8530_write_port(c->ctrlio, val);
+}
+
+/**
+ * write_zsdata - Write to a Z8530 control register
+ * @c: The Z8530 channel
+ * @val: Value to write
+ *
+ * Write directly to the data register on the Z8530
+ */
+
+
+static inline void write_zsdata(struct z8530_channel *c, u8 val)
+{
+ z8530_write_port(c->dataio, val);
+}
+
+/*
+ * Register loading parameters for a dead port
+ */
+
+u8 z8530_dead_port[]=
+{
+ 255
+};
+
+EXPORT_SYMBOL(z8530_dead_port);
+
+/*
+ * Register loading parameters for currently supported circuit types
+ */
+
+
+/*
+ * Data clocked by telco end. This is the correct data for the UK
+ * "kilostream" service, and most other similar services.
+ */
+
+u8 z8530_hdlc_kilostream[]=
+{
+ 4, SYNC_ENAB|SDLC|X1CLK,
+ 2, 0, /* No vector */
+ 1, 0,
+ 3, ENT_HM|RxCRC_ENAB|Rx8,
+ 5, TxCRC_ENAB|RTS|TxENAB|Tx8|DTR,
+ 9, 0, /* Disable interrupts */
+ 6, 0xFF,
+ 7, FLAG,
+ 10, ABUNDER|NRZ|CRCPS,/*MARKIDLE ??*/
+ 11, TCTRxCP,
+ 14, DISDPLL,
+ 15, DCDIE|SYNCIE|CTSIE|TxUIE|BRKIE,
+ 1, EXT_INT_ENAB|TxINT_ENAB|INT_ALL_Rx,
+ 9, NV|MIE|NORESET,
+ 255
+};
+
+EXPORT_SYMBOL(z8530_hdlc_kilostream);
+
+/*
+ * As above but for enhanced chips.
+ */
+
+u8 z8530_hdlc_kilostream_85230[]=
+{
+ 4, SYNC_ENAB|SDLC|X1CLK,
+ 2, 0, /* No vector */
+ 1, 0,
+ 3, ENT_HM|RxCRC_ENAB|Rx8,
+ 5, TxCRC_ENAB|RTS|TxENAB|Tx8|DTR,
+ 9, 0, /* Disable interrupts */
+ 6, 0xFF,
+ 7, FLAG,
+ 10, ABUNDER|NRZ|CRCPS, /* MARKIDLE?? */
+ 11, TCTRxCP,
+ 14, DISDPLL,
+ 15, DCDIE|SYNCIE|CTSIE|TxUIE|BRKIE,
+ 1, EXT_INT_ENAB|TxINT_ENAB|INT_ALL_Rx,
+ 9, NV|MIE|NORESET,
+ 23, 3, /* Extended mode AUTO TX and EOM*/
+
+ 255
+};
+
+EXPORT_SYMBOL(z8530_hdlc_kilostream_85230);
+
+/**
+ * z8530_flush_fifo - Flush on chip RX FIFO
+ * @c: Channel to flush
+ *
+ * Flush the receive FIFO. There is no specific option for this, we
+ * blindly read bytes and discard them. Reading when there is no data
+ * is harmless. The 8530 has a 4 byte FIFO, the 85230 has 8 bytes.
+ *
+ * All locking is handled for the caller. On return data may still be
+ * present if it arrived during the flush.
+ */
+
+static void z8530_flush_fifo(struct z8530_channel *c)
+{
+ read_zsreg(c, R1);
+ read_zsreg(c, R1);
+ read_zsreg(c, R1);
+ read_zsreg(c, R1);
+ if(c->dev->type==Z85230)
+ {
+ read_zsreg(c, R1);
+ read_zsreg(c, R1);
+ read_zsreg(c, R1);
+ read_zsreg(c, R1);
+ }
+}
+
+/**
+ * z8530_rtsdtr - Control the outgoing DTS/RTS line
+ * @c: The Z8530 channel to control;
+ * @set: 1 to set, 0 to clear
+ *
+ * Sets or clears DTR/RTS on the requested line. All locking is handled
+ * by the caller. For now we assume all boards use the actual RTS/DTR
+ * on the chip. Apparently one or two don't. We'll scream about them
+ * later.
+ */
+
+static void z8530_rtsdtr(struct z8530_channel *c, int set)
+{
+ if (set)
+ c->regs[5] |= (RTS | DTR);
+ else
+ c->regs[5] &= ~(RTS | DTR);
+ write_zsreg(c, R5, c->regs[5]);
+}
+
+/**
+ * z8530_rx - Handle a PIO receive event
+ * @c: Z8530 channel to process
+ *
+ * Receive handler for receiving in PIO mode. This is much like the
+ * async one but not quite the same or as complex
+ *
+ * Note: Its intended that this handler can easily be separated from
+ * the main code to run realtime. That'll be needed for some machines
+ * (eg to ever clock 64kbits on a sparc ;)).
+ *
+ * The RT_LOCK macros don't do anything now. Keep the code covered
+ * by them as short as possible in all circumstances - clocks cost
+ * baud. The interrupt handler is assumed to be atomic w.r.t. to
+ * other code - this is true in the RT case too.
+ *
+ * We only cover the sync cases for this. If you want 2Mbit async
+ * do it yourself but consider medical assistance first. This non DMA
+ * synchronous mode is portable code. The DMA mode assumes PCI like
+ * ISA DMA
+ *
+ * Called with the device lock held
+ */
+
+static void z8530_rx(struct z8530_channel *c)
+{
+ u8 ch,stat;
+ spin_lock(c->lock);
+
+ while(1)
+ {
+ /* FIFO empty ? */
+ if(!(read_zsreg(c, R0)&1))
+ break;
+ ch=read_zsdata(c);
+ stat=read_zsreg(c, R1);
+
+ /*
+ * Overrun ?
+ */
+ if(c->count < c->max)
+ {
+ *c->dptr++=ch;
+ c->count++;
+ }
+
+ if(stat&END_FR)
+ {
+
+ /*
+ * Error ?
+ */
+ if(stat&(Rx_OVR|CRC_ERR))
+ {
+ /* Rewind the buffer and return */
+ if(c->skb)
+ c->dptr=c->skb->data;
+ c->count=0;
+ if(stat&Rx_OVR)
+ {
+ printk(KERN_WARNING "%s: overrun\n", c->dev->name);
+ c->rx_overrun++;
+ }
+ if(stat&CRC_ERR)
+ {
+ c->rx_crc_err++;
+ /* printk("crc error\n"); */
+ }
+ /* Shove the frame upstream */
+ }
+ else
+ {
+ /*
+ * Drop the lock for RX processing, or
+ * there are deadlocks
+ */
+ z8530_rx_done(c);
+ write_zsctrl(c, RES_Rx_CRC);
+ }
+ }
+ }
+ /*
+ * Clear irq
+ */
+ write_zsctrl(c, ERR_RES);
+ write_zsctrl(c, RES_H_IUS);
+ spin_unlock(c->lock);
+}
+
+
+/**
+ * z8530_tx - Handle a PIO transmit event
+ * @c: Z8530 channel to process
+ *
+ * Z8530 transmit interrupt handler for the PIO mode. The basic
+ * idea is to attempt to keep the FIFO fed. We fill as many bytes
+ * in as possible, its quite possible that we won't keep up with the
+ * data rate otherwise.
+ */
+
+static void z8530_tx(struct z8530_channel *c)
+{
+ spin_lock(c->lock);
+ while(c->txcount) {
+ /* FIFO full ? */
+ if(!(read_zsreg(c, R0)&4))
+ break;
+ c->txcount--;
+ /*
+ * Shovel out the byte
+ */
+ write_zsreg(c, R8, *c->tx_ptr++);
+ write_zsctrl(c, RES_H_IUS);
+ /* We are about to underflow */
+ if(c->txcount==0)
+ {
+ write_zsctrl(c, RES_EOM_L);
+ write_zsreg(c, R10, c->regs[10]&~ABUNDER);
+ }
+ }
+
+
+ /*
+ * End of frame TX - fire another one
+ */
+
+ write_zsctrl(c, RES_Tx_P);
+
+ z8530_tx_done(c);
+ write_zsctrl(c, RES_H_IUS);
+ spin_unlock(c->lock);
+}
+
+/**
+ * z8530_status - Handle a PIO status exception
+ * @chan: Z8530 channel to process
+ *
+ * A status event occurred in PIO synchronous mode. There are several
+ * reasons the chip will bother us here. A transmit underrun means we
+ * failed to feed the chip fast enough and just broke a packet. A DCD
+ * change is a line up or down. We communicate that back to the protocol
+ * layer for synchronous PPP to renegotiate.
+ */
+
+static void z8530_status(struct z8530_channel *chan)
+{
+ u8 status, altered;
+
+ spin_lock(chan->lock);
+ status=read_zsreg(chan, R0);
+ altered=chan->status^status;
+
+ chan->status=status;
+
+ if(status&TxEOM)
+ {
+/* printk("%s: Tx underrun.\n", chan->dev->name); */
+ chan->stats.tx_fifo_errors++;
+ write_zsctrl(chan, ERR_RES);
+ z8530_tx_done(chan);
+ }
+
+ if(altered&chan->dcdcheck)
+ {
+ if(status&chan->dcdcheck)
+ {
+ printk(KERN_INFO "%s: DCD raised\n", chan->dev->name);
+ write_zsreg(chan, R3, chan->regs[3]|RxENABLE);
+ if(chan->netdevice &&
+ ((chan->netdevice->type == ARPHRD_HDLC) ||
+ (chan->netdevice->type == ARPHRD_PPP)))
+ sppp_reopen(chan->netdevice);
+ }
+ else
+ {
+ printk(KERN_INFO "%s: DCD lost\n", chan->dev->name);
+ write_zsreg(chan, R3, chan->regs[3]&~RxENABLE);
+ z8530_flush_fifo(chan);
+ }
+
+ }
+ write_zsctrl(chan, RES_EXT_INT);
+ write_zsctrl(chan, RES_H_IUS);
+ spin_unlock(chan->lock);
+}
+
+struct z8530_irqhandler z8530_sync=
+{
+ z8530_rx,
+ z8530_tx,
+ z8530_status
+};
+
+EXPORT_SYMBOL(z8530_sync);
+
+/**
+ * z8530_dma_rx - Handle a DMA RX event
+ * @chan: Channel to handle
+ *
+ * Non bus mastering DMA interfaces for the Z8x30 devices. This
+ * is really pretty PC specific. The DMA mode means that most receive
+ * events are handled by the DMA hardware. We get a kick here only if
+ * a frame ended.
+ */
+
+static void z8530_dma_rx(struct z8530_channel *chan)
+{
+ spin_lock(chan->lock);
+ if(chan->rxdma_on)
+ {
+ /* Special condition check only */
+ u8 status;
+
+ read_zsreg(chan, R7);
+ read_zsreg(chan, R6);
+
+ status=read_zsreg(chan, R1);
+
+ if(status&END_FR)
+ {
+ z8530_rx_done(chan); /* Fire up the next one */
+ }
+ write_zsctrl(chan, ERR_RES);
+ write_zsctrl(chan, RES_H_IUS);
+ }
+ else
+ {
+ /* DMA is off right now, drain the slow way */
+ z8530_rx(chan);
+ }
+ spin_unlock(chan->lock);
+}
+
+/**
+ * z8530_dma_tx - Handle a DMA TX event
+ * @chan: The Z8530 channel to handle
+ *
+ * We have received an interrupt while doing DMA transmissions. It
+ * shouldn't happen. Scream loudly if it does.
+ */
+
+static void z8530_dma_tx(struct z8530_channel *chan)
+{
+ spin_lock(chan->lock);
+ if(!chan->dma_tx)
+ {
+ printk(KERN_WARNING "Hey who turned the DMA off?\n");
+ z8530_tx(chan);
+ return;
+ }
+ /* This shouldnt occur in DMA mode */
+ printk(KERN_ERR "DMA tx - bogus event!\n");
+ z8530_tx(chan);
+ spin_unlock(chan->lock);
+}
+
+/**
+ * z8530_dma_status - Handle a DMA status exception
+ * @chan: Z8530 channel to process
+ *
+ * A status event occurred on the Z8530. We receive these for two reasons
+ * when in DMA mode. Firstly if we finished a packet transfer we get one
+ * and kick the next packet out. Secondly we may see a DCD change and
+ * have to poke the protocol layer.
+ *
+ */
+
+static void z8530_dma_status(struct z8530_channel *chan)
+{
+ u8 status, altered;
+
+ status=read_zsreg(chan, R0);
+ altered=chan->status^status;
+
+ chan->status=status;
+
+
+ if(chan->dma_tx)
+ {
+ if(status&TxEOM)
+ {
+ unsigned long flags;
+
+ flags=claim_dma_lock();
+ disable_dma(chan->txdma);
+ clear_dma_ff(chan->txdma);
+ chan->txdma_on=0;
+ release_dma_lock(flags);
+ z8530_tx_done(chan);
+ }
+ }
+
+ spin_lock(chan->lock);
+ if(altered&chan->dcdcheck)
+ {
+ if(status&chan->dcdcheck)
+ {
+ printk(KERN_INFO "%s: DCD raised\n", chan->dev->name);
+ write_zsreg(chan, R3, chan->regs[3]|RxENABLE);
+ if(chan->netdevice &&
+ ((chan->netdevice->type == ARPHRD_HDLC) ||
+ (chan->netdevice->type == ARPHRD_PPP)))
+ sppp_reopen(chan->netdevice);
+ }
+ else
+ {
+ printk(KERN_INFO "%s:DCD lost\n", chan->dev->name);
+ write_zsreg(chan, R3, chan->regs[3]&~RxENABLE);
+ z8530_flush_fifo(chan);
+ }
+ }
+
+ write_zsctrl(chan, RES_EXT_INT);
+ write_zsctrl(chan, RES_H_IUS);
+ spin_unlock(chan->lock);
+}
+
+struct z8530_irqhandler z8530_dma_sync=
+{
+ z8530_dma_rx,
+ z8530_dma_tx,
+ z8530_dma_status
+};
+
+EXPORT_SYMBOL(z8530_dma_sync);
+
+struct z8530_irqhandler z8530_txdma_sync=
+{
+ z8530_rx,
+ z8530_dma_tx,
+ z8530_dma_status
+};
+
+EXPORT_SYMBOL(z8530_txdma_sync);
+
+/**
+ * z8530_rx_clear - Handle RX events from a stopped chip
+ * @c: Z8530 channel to shut up
+ *
+ * Receive interrupt vectors for a Z8530 that is in 'parked' mode.
+ * For machines with PCI Z85x30 cards, or level triggered interrupts
+ * (eg the MacII) we must clear the interrupt cause or die.
+ */
+
+
+static void z8530_rx_clear(struct z8530_channel *c)
+{
+ /*
+ * Data and status bytes
+ */
+ u8 stat;
+
+ read_zsdata(c);
+ stat=read_zsreg(c, R1);
+
+ if(stat&END_FR)
+ write_zsctrl(c, RES_Rx_CRC);
+ /*
+ * Clear irq
+ */
+ write_zsctrl(c, ERR_RES);
+ write_zsctrl(c, RES_H_IUS);
+}
+
+/**
+ * z8530_tx_clear - Handle TX events from a stopped chip
+ * @c: Z8530 channel to shut up
+ *
+ * Transmit interrupt vectors for a Z8530 that is in 'parked' mode.
+ * For machines with PCI Z85x30 cards, or level triggered interrupts
+ * (eg the MacII) we must clear the interrupt cause or die.
+ */
+
+static void z8530_tx_clear(struct z8530_channel *c)
+{
+ write_zsctrl(c, RES_Tx_P);
+ write_zsctrl(c, RES_H_IUS);
+}
+
+/**
+ * z8530_status_clear - Handle status events from a stopped chip
+ * @chan: Z8530 channel to shut up
+ *
+ * Status interrupt vectors for a Z8530 that is in 'parked' mode.
+ * For machines with PCI Z85x30 cards, or level triggered interrupts
+ * (eg the MacII) we must clear the interrupt cause or die.
+ */
+
+static void z8530_status_clear(struct z8530_channel *chan)
+{
+ u8 status=read_zsreg(chan, R0);
+ if(status&TxEOM)
+ write_zsctrl(chan, ERR_RES);
+ write_zsctrl(chan, RES_EXT_INT);
+ write_zsctrl(chan, RES_H_IUS);
+}
+
+struct z8530_irqhandler z8530_nop=
+{
+ z8530_rx_clear,
+ z8530_tx_clear,
+ z8530_status_clear
+};
+
+
+EXPORT_SYMBOL(z8530_nop);
+
+/**
+ * z8530_interrupt - Handle an interrupt from a Z8530
+ * @irq: Interrupt number
+ * @dev_id: The Z8530 device that is interrupting.
+ * @regs: unused
+ *
+ * A Z85[2]30 device has stuck its hand in the air for attention.
+ * We scan both the channels on the chip for events and then call
+ * the channel specific call backs for each channel that has events.
+ * We have to use callback functions because the two channels can be
+ * in different modes.
+ *
+ * Locking is done for the handlers. Note that locking is done
+ * at the chip level (the 5uS delay issue is per chip not per
+ * channel). c->lock for both channels points to dev->lock
+ */
+
+irqreturn_t z8530_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct z8530_dev *dev=dev_id;
+ u8 intr;
+ static volatile int locker=0;
+ int work=0;
+ struct z8530_irqhandler *irqs;
+
+ if(locker)
+ {
+ printk(KERN_ERR "IRQ re-enter\n");
+ return IRQ_NONE;
+ }
+ locker=1;
+
+ spin_lock(&dev->lock);
+
+ while(++work<5000)
+ {
+
+ intr = read_zsreg(&dev->chanA, R3);
+ if(!(intr & (CHARxIP|CHATxIP|CHAEXT|CHBRxIP|CHBTxIP|CHBEXT)))
+ break;
+
+ /* This holds the IRQ status. On the 8530 you must read it from chan
+ A even though it applies to the whole chip */
+
+ /* Now walk the chip and see what it is wanting - it may be
+ an IRQ for someone else remember */
+
+ irqs=dev->chanA.irqs;
+
+ if(intr & (CHARxIP|CHATxIP|CHAEXT))
+ {
+ if(intr&CHARxIP)
+ irqs->rx(&dev->chanA);
+ if(intr&CHATxIP)
+ irqs->tx(&dev->chanA);
+ if(intr&CHAEXT)
+ irqs->status(&dev->chanA);
+ }
+
+ irqs=dev->chanB.irqs;
+
+ if(intr & (CHBRxIP|CHBTxIP|CHBEXT))
+ {
+ if(intr&CHBRxIP)
+ irqs->rx(&dev->chanB);
+ if(intr&CHBTxIP)
+ irqs->tx(&dev->chanB);
+ if(intr&CHBEXT)
+ irqs->status(&dev->chanB);
+ }
+ }
+ spin_unlock(&dev->lock);
+ if(work==5000)
+ printk(KERN_ERR "%s: interrupt jammed - abort(0x%X)!\n", dev->name, intr);
+ /* Ok all done */
+ locker=0;
+ return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(z8530_interrupt);
+
+static char reg_init[16]=
+{
+ 0,0,0,0,
+ 0,0,0,0,
+ 0,0,0,0,
+ 0x55,0,0,0
+};
+
+
+/**
+ * z8530_sync_open - Open a Z8530 channel for PIO
+ * @dev: The network interface we are using
+ * @c: The Z8530 channel to open in synchronous PIO mode
+ *
+ * Switch a Z8530 into synchronous mode without DMA assist. We
+ * raise the RTS/DTR and commence network operation.
+ */
+
+int z8530_sync_open(struct net_device *dev, struct z8530_channel *c)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(c->lock, flags);
+
+ c->sync = 1;
+ c->mtu = dev->mtu+64;
+ c->count = 0;
+ c->skb = NULL;
+ c->skb2 = NULL;
+ c->irqs = &z8530_sync;
+
+ /* This loads the double buffer up */
+ z8530_rx_done(c); /* Load the frame ring */
+ z8530_rx_done(c); /* Load the backup frame */
+ z8530_rtsdtr(c,1);
+ c->dma_tx = 0;
+ c->regs[R1]|=TxINT_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+ write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+
+ spin_unlock_irqrestore(c->lock, flags);
+ return 0;
+}
+
+
+EXPORT_SYMBOL(z8530_sync_open);
+
+/**
+ * z8530_sync_close - Close a PIO Z8530 channel
+ * @dev: Network device to close
+ * @c: Z8530 channel to disassociate and move to idle
+ *
+ * Close down a Z8530 interface and switch its interrupt handlers
+ * to discard future events.
+ */
+
+int z8530_sync_close(struct net_device *dev, struct z8530_channel *c)
+{
+ u8 chk;
+ unsigned long flags;
+
+ spin_lock_irqsave(c->lock, flags);
+ c->irqs = &z8530_nop;
+ c->max = 0;
+ c->sync = 0;
+
+ chk=read_zsreg(c,R0);
+ write_zsreg(c, R3, c->regs[R3]);
+ z8530_rtsdtr(c,0);
+
+ spin_unlock_irqrestore(c->lock, flags);
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_close);
+
+/**
+ * z8530_sync_dma_open - Open a Z8530 for DMA I/O
+ * @dev: The network device to attach
+ * @c: The Z8530 channel to configure in sync DMA mode.
+ *
+ * Set up a Z85x30 device for synchronous DMA in both directions. Two
+ * ISA DMA channels must be available for this to work. We assume ISA
+ * DMA driven I/O and PC limits on access.
+ */
+
+int z8530_sync_dma_open(struct net_device *dev, struct z8530_channel *c)
+{
+ unsigned long cflags, dflags;
+
+ c->sync = 1;
+ c->mtu = dev->mtu+64;
+ c->count = 0;
+ c->skb = NULL;
+ c->skb2 = NULL;
+ /*
+ * Load the DMA interfaces up
+ */
+ c->rxdma_on = 0;
+ c->txdma_on = 0;
+
+ /*
+ * Allocate the DMA flip buffers. Limit by page size.
+ * Everyone runs 1500 mtu or less on wan links so this
+ * should be fine.
+ */
+
+ if(c->mtu > PAGE_SIZE/2)
+ return -EMSGSIZE;
+
+ c->rx_buf[0]=(void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
+ if(c->rx_buf[0]==NULL)
+ return -ENOBUFS;
+ c->rx_buf[1]=c->rx_buf[0]+PAGE_SIZE/2;
+
+ c->tx_dma_buf[0]=(void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
+ if(c->tx_dma_buf[0]==NULL)
+ {
+ free_page((unsigned long)c->rx_buf[0]);
+ c->rx_buf[0]=NULL;
+ return -ENOBUFS;
+ }
+ c->tx_dma_buf[1]=c->tx_dma_buf[0]+PAGE_SIZE/2;
+
+ c->tx_dma_used=0;
+ c->dma_tx = 1;
+ c->dma_num=0;
+ c->dma_ready=1;
+
+ /*
+ * Enable DMA control mode
+ */
+
+ spin_lock_irqsave(c->lock, cflags);
+
+ /*
+ * TX DMA via DIR/REQ
+ */
+
+ c->regs[R14]|= DTRREQ;
+ write_zsreg(c, R14, c->regs[R14]);
+
+ c->regs[R1]&= ~TxINT_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+
+ /*
+ * RX DMA via W/Req
+ */
+
+ c->regs[R1]|= WT_FN_RDYFN;
+ c->regs[R1]|= WT_RDY_RT;
+ c->regs[R1]|= INT_ERR_Rx;
+ c->regs[R1]&= ~TxINT_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+ c->regs[R1]|= WT_RDY_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+
+ /*
+ * DMA interrupts
+ */
+
+ /*
+ * Set up the DMA configuration
+ */
+
+ dflags=claim_dma_lock();
+
+ disable_dma(c->rxdma);
+ clear_dma_ff(c->rxdma);
+ set_dma_mode(c->rxdma, DMA_MODE_READ|0x10);
+ set_dma_addr(c->rxdma, virt_to_bus(c->rx_buf[0]));
+ set_dma_count(c->rxdma, c->mtu);
+ enable_dma(c->rxdma);
+
+ disable_dma(c->txdma);
+ clear_dma_ff(c->txdma);
+ set_dma_mode(c->txdma, DMA_MODE_WRITE);
+ disable_dma(c->txdma);
+
+ release_dma_lock(dflags);
+
+ /*
+ * Select the DMA interrupt handlers
+ */
+
+ c->rxdma_on = 1;
+ c->txdma_on = 1;
+ c->tx_dma_used = 1;
+
+ c->irqs = &z8530_dma_sync;
+ z8530_rtsdtr(c,1);
+ write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+
+ spin_unlock_irqrestore(c->lock, cflags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_dma_open);
+
+/**
+ * z8530_sync_dma_close - Close down DMA I/O
+ * @dev: Network device to detach
+ * @c: Z8530 channel to move into discard mode
+ *
+ * Shut down a DMA mode synchronous interface. Halt the DMA, and
+ * free the buffers.
+ */
+
+int z8530_sync_dma_close(struct net_device *dev, struct z8530_channel *c)
+{
+ u8 chk;
+ unsigned long flags;
+
+ c->irqs = &z8530_nop;
+ c->max = 0;
+ c->sync = 0;
+
+ /*
+ * Disable the PC DMA channels
+ */
+
+ flags=claim_dma_lock();
+ disable_dma(c->rxdma);
+ clear_dma_ff(c->rxdma);
+
+ c->rxdma_on = 0;
+
+ disable_dma(c->txdma);
+ clear_dma_ff(c->txdma);
+ release_dma_lock(flags);
+
+ c->txdma_on = 0;
+ c->tx_dma_used = 0;
+
+ spin_lock_irqsave(c->lock, flags);
+
+ /*
+ * Disable DMA control mode
+ */
+
+ c->regs[R1]&= ~WT_RDY_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+ c->regs[R1]&= ~(WT_RDY_RT|WT_FN_RDYFN|INT_ERR_Rx);
+ c->regs[R1]|= INT_ALL_Rx;
+ write_zsreg(c, R1, c->regs[R1]);
+ c->regs[R14]&= ~DTRREQ;
+ write_zsreg(c, R14, c->regs[R14]);
+
+ if(c->rx_buf[0])
+ {
+ free_page((unsigned long)c->rx_buf[0]);
+ c->rx_buf[0]=NULL;
+ }
+ if(c->tx_dma_buf[0])
+ {
+ free_page((unsigned long)c->tx_dma_buf[0]);
+ c->tx_dma_buf[0]=NULL;
+ }
+ chk=read_zsreg(c,R0);
+ write_zsreg(c, R3, c->regs[R3]);
+ z8530_rtsdtr(c,0);
+
+ spin_unlock_irqrestore(c->lock, flags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_dma_close);
+
+/**
+ * z8530_sync_txdma_open - Open a Z8530 for TX driven DMA
+ * @dev: The network device to attach
+ * @c: The Z8530 channel to configure in sync DMA mode.
+ *
+ * Set up a Z85x30 device for synchronous DMA tranmission. One
+ * ISA DMA channel must be available for this to work. The receive
+ * side is run in PIO mode, but then it has the bigger FIFO.
+ */
+
+int z8530_sync_txdma_open(struct net_device *dev, struct z8530_channel *c)
+{
+ unsigned long cflags, dflags;
+
+ printk("Opening sync interface for TX-DMA\n");
+ c->sync = 1;
+ c->mtu = dev->mtu+64;
+ c->count = 0;
+ c->skb = NULL;
+ c->skb2 = NULL;
+
+ /*
+ * Allocate the DMA flip buffers. Limit by page size.
+ * Everyone runs 1500 mtu or less on wan links so this
+ * should be fine.
+ */
+
+ if(c->mtu > PAGE_SIZE/2)
+ return -EMSGSIZE;
+
+ c->tx_dma_buf[0]=(void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
+ if(c->tx_dma_buf[0]==NULL)
+ return -ENOBUFS;
+
+ c->tx_dma_buf[1] = c->tx_dma_buf[0] + PAGE_SIZE/2;
+
+
+ spin_lock_irqsave(c->lock, cflags);
+
+ /*
+ * Load the PIO receive ring
+ */
+
+ z8530_rx_done(c);
+ z8530_rx_done(c);
+
+ /*
+ * Load the DMA interfaces up
+ */
+
+ c->rxdma_on = 0;
+ c->txdma_on = 0;
+
+ c->tx_dma_used=0;
+ c->dma_num=0;
+ c->dma_ready=1;
+ c->dma_tx = 1;
+
+ /*
+ * Enable DMA control mode
+ */
+
+ /*
+ * TX DMA via DIR/REQ
+ */
+ c->regs[R14]|= DTRREQ;
+ write_zsreg(c, R14, c->regs[R14]);
+
+ c->regs[R1]&= ~TxINT_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+
+ /*
+ * Set up the DMA configuration
+ */
+
+ dflags = claim_dma_lock();
+
+ disable_dma(c->txdma);
+ clear_dma_ff(c->txdma);
+ set_dma_mode(c->txdma, DMA_MODE_WRITE);
+ disable_dma(c->txdma);
+
+ release_dma_lock(dflags);
+
+ /*
+ * Select the DMA interrupt handlers
+ */
+
+ c->rxdma_on = 0;
+ c->txdma_on = 1;
+ c->tx_dma_used = 1;
+
+ c->irqs = &z8530_txdma_sync;
+ z8530_rtsdtr(c,1);
+ write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+ spin_unlock_irqrestore(c->lock, cflags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_sync_txdma_open);
+
+/**
+ * z8530_sync_txdma_close - Close down a TX driven DMA channel
+ * @dev: Network device to detach
+ * @c: Z8530 channel to move into discard mode
+ *
+ * Shut down a DMA/PIO split mode synchronous interface. Halt the DMA,
+ * and free the buffers.
+ */
+
+int z8530_sync_txdma_close(struct net_device *dev, struct z8530_channel *c)
+{
+ unsigned long dflags, cflags;
+ u8 chk;
+
+
+ spin_lock_irqsave(c->lock, cflags);
+
+ c->irqs = &z8530_nop;
+ c->max = 0;
+ c->sync = 0;
+
+ /*
+ * Disable the PC DMA channels
+ */
+
+ dflags = claim_dma_lock();
+
+ disable_dma(c->txdma);
+ clear_dma_ff(c->txdma);
+ c->txdma_on = 0;
+ c->tx_dma_used = 0;
+
+ release_dma_lock(dflags);
+
+ /*
+ * Disable DMA control mode
+ */
+
+ c->regs[R1]&= ~WT_RDY_ENAB;
+ write_zsreg(c, R1, c->regs[R1]);
+ c->regs[R1]&= ~(WT_RDY_RT|WT_FN_RDYFN|INT_ERR_Rx);
+ c->regs[R1]|= INT_ALL_Rx;
+ write_zsreg(c, R1, c->regs[R1]);
+ c->regs[R14]&= ~DTRREQ;
+ write_zsreg(c, R14, c->regs[R14]);
+
+ if(c->tx_dma_buf[0])
+ {
+ free_page((unsigned long)c->tx_dma_buf[0]);
+ c->tx_dma_buf[0]=NULL;
+ }
+ chk=read_zsreg(c,R0);
+ write_zsreg(c, R3, c->regs[R3]);
+ z8530_rtsdtr(c,0);
+
+ spin_unlock_irqrestore(c->lock, cflags);
+ return 0;
+}
+
+
+EXPORT_SYMBOL(z8530_sync_txdma_close);
+
+
+/*
+ * Name strings for Z8530 chips. SGI claim to have a 130, Zilog deny
+ * it exists...
+ */
+
+static char *z8530_type_name[]={
+ "Z8530",
+ "Z85C30",
+ "Z85230"
+};
+
+/**
+ * z8530_describe - Uniformly describe a Z8530 port
+ * @dev: Z8530 device to describe
+ * @mapping: string holding mapping type (eg "I/O" or "Mem")
+ * @io: the port value in question
+ *
+ * Describe a Z8530 in a standard format. We must pass the I/O as
+ * the port offset isnt predictable. The main reason for this function
+ * is to try and get a common format of report.
+ */
+
+void z8530_describe(struct z8530_dev *dev, char *mapping, unsigned long io)
+{
+ printk(KERN_INFO "%s: %s found at %s 0x%lX, IRQ %d.\n",
+ dev->name,
+ z8530_type_name[dev->type],
+ mapping,
+ Z8530_PORT_OF(io),
+ dev->irq);
+}
+
+EXPORT_SYMBOL(z8530_describe);
+
+/*
+ * Locked operation part of the z8530 init code
+ */
+
+static inline int do_z8530_init(struct z8530_dev *dev)
+{
+ /* NOP the interrupt handlers first - we might get a
+ floating IRQ transition when we reset the chip */
+ dev->chanA.irqs=&z8530_nop;
+ dev->chanB.irqs=&z8530_nop;
+ dev->chanA.dcdcheck=DCD;
+ dev->chanB.dcdcheck=DCD;
+
+ /* Reset the chip */
+ write_zsreg(&dev->chanA, R9, 0xC0);
+ udelay(200);
+ /* Now check its valid */
+ write_zsreg(&dev->chanA, R12, 0xAA);
+ if(read_zsreg(&dev->chanA, R12)!=0xAA)
+ return -ENODEV;
+ write_zsreg(&dev->chanA, R12, 0x55);
+ if(read_zsreg(&dev->chanA, R12)!=0x55)
+ return -ENODEV;
+
+ dev->type=Z8530;
+
+ /*
+ * See the application note.
+ */
+
+ write_zsreg(&dev->chanA, R15, 0x01);
+
+ /*
+ * If we can set the low bit of R15 then
+ * the chip is enhanced.
+ */
+
+ if(read_zsreg(&dev->chanA, R15)==0x01)
+ {
+ /* This C30 versus 230 detect is from Klaus Kudielka's dmascc */
+ /* Put a char in the fifo */
+ write_zsreg(&dev->chanA, R8, 0);
+ if(read_zsreg(&dev->chanA, R0)&Tx_BUF_EMP)
+ dev->type = Z85230; /* Has a FIFO */
+ else
+ dev->type = Z85C30; /* Z85C30, 1 byte FIFO */
+ }
+
+ /*
+ * The code assumes R7' and friends are
+ * off. Use write_zsext() for these and keep
+ * this bit clear.
+ */
+
+ write_zsreg(&dev->chanA, R15, 0);
+
+ /*
+ * At this point it looks like the chip is behaving
+ */
+
+ memcpy(dev->chanA.regs, reg_init, 16);
+ memcpy(dev->chanB.regs, reg_init ,16);
+
+ return 0;
+}
+
+/**
+ * z8530_init - Initialise a Z8530 device
+ * @dev: Z8530 device to initialise.
+ *
+ * Configure up a Z8530/Z85C30 or Z85230 chip. We check the device
+ * is present, identify the type and then program it to hopefully
+ * keep quite and behave. This matters a lot, a Z8530 in the wrong
+ * state will sometimes get into stupid modes generating 10Khz
+ * interrupt streams and the like.
+ *
+ * We set the interrupt handler up to discard any events, in case
+ * we get them during reset or setp.
+ *
+ * Return 0 for success, or a negative value indicating the problem
+ * in errno form.
+ */
+
+int z8530_init(struct z8530_dev *dev)
+{
+ unsigned long flags;
+ int ret;
+
+ /* Set up the chip level lock */
+ spin_lock_init(&dev->lock);
+ dev->chanA.lock = &dev->lock;
+ dev->chanB.lock = &dev->lock;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ ret = do_z8530_init(dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return ret;
+}
+
+
+EXPORT_SYMBOL(z8530_init);
+
+/**
+ * z8530_shutdown - Shutdown a Z8530 device
+ * @dev: The Z8530 chip to shutdown
+ *
+ * We set the interrupt handlers to silence any interrupts. We then
+ * reset the chip and wait 100uS to be sure the reset completed. Just
+ * in case the caller then tries to do stuff.
+ *
+ * This is called without the lock held
+ */
+
+int z8530_shutdown(struct z8530_dev *dev)
+{
+ unsigned long flags;
+ /* Reset the chip */
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->chanA.irqs=&z8530_nop;
+ dev->chanB.irqs=&z8530_nop;
+ write_zsreg(&dev->chanA, R9, 0xC0);
+ /* We must lock the udelay, the chip is offlimits here */
+ udelay(100);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_shutdown);
+
+/**
+ * z8530_channel_load - Load channel data
+ * @c: Z8530 channel to configure
+ * @rtable: table of register, value pairs
+ * FIXME: ioctl to allow user uploaded tables
+ *
+ * Load a Z8530 channel up from the system data. We use +16 to
+ * indicate the "prime" registers. The value 255 terminates the
+ * table.
+ */
+
+int z8530_channel_load(struct z8530_channel *c, u8 *rtable)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(c->lock, flags);
+
+ while(*rtable!=255)
+ {
+ int reg=*rtable++;
+ if(reg>0x0F)
+ write_zsreg(c, R15, c->regs[15]|1);
+ write_zsreg(c, reg&0x0F, *rtable);
+ if(reg>0x0F)
+ write_zsreg(c, R15, c->regs[15]&~1);
+ c->regs[reg]=*rtable++;
+ }
+ c->rx_function=z8530_null_rx;
+ c->skb=NULL;
+ c->tx_skb=NULL;
+ c->tx_next_skb=NULL;
+ c->mtu=1500;
+ c->max=0;
+ c->count=0;
+ c->status=read_zsreg(c, R0);
+ c->sync=1;
+ write_zsreg(c, R3, c->regs[R3]|RxENABLE);
+
+ spin_unlock_irqrestore(c->lock, flags);
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_channel_load);
+
+
+/**
+ * z8530_tx_begin - Begin packet transmission
+ * @c: The Z8530 channel to kick
+ *
+ * This is the speed sensitive side of transmission. If we are called
+ * and no buffer is being transmitted we commence the next buffer. If
+ * nothing is queued we idle the sync.
+ *
+ * Note: We are handling this code path in the interrupt path, keep it
+ * fast or bad things will happen.
+ *
+ * Called with the lock held.
+ */
+
+static void z8530_tx_begin(struct z8530_channel *c)
+{
+ unsigned long flags;
+ if(c->tx_skb)
+ return;
+
+ c->tx_skb=c->tx_next_skb;
+ c->tx_next_skb=NULL;
+ c->tx_ptr=c->tx_next_ptr;
+
+ if(c->tx_skb==NULL)
+ {
+ /* Idle on */
+ if(c->dma_tx)
+ {
+ flags=claim_dma_lock();
+ disable_dma(c->txdma);
+ /*
+ * Check if we crapped out.
+ */
+ if(get_dma_residue(c->txdma))
+ {
+ c->stats.tx_dropped++;
+ c->stats.tx_fifo_errors++;
+ }
+ release_dma_lock(flags);
+ }
+ c->txcount=0;
+ }
+ else
+ {
+ c->txcount=c->tx_skb->len;
+
+
+ if(c->dma_tx)
+ {
+ /*
+ * FIXME. DMA is broken for the original 8530,
+ * on the older parts we need to set a flag and
+ * wait for a further TX interrupt to fire this
+ * stage off
+ */
+
+ flags=claim_dma_lock();
+ disable_dma(c->txdma);
+
+ /*
+ * These two are needed by the 8530/85C30
+ * and must be issued when idling.
+ */
+
+ if(c->dev->type!=Z85230)
+ {
+ write_zsctrl(c, RES_Tx_CRC);
+ write_zsctrl(c, RES_EOM_L);
+ }
+ write_zsreg(c, R10, c->regs[10]&~ABUNDER);
+ clear_dma_ff(c->txdma);
+ set_dma_addr(c->txdma, virt_to_bus(c->tx_ptr));
+ set_dma_count(c->txdma, c->txcount);
+ enable_dma(c->txdma);
+ release_dma_lock(flags);
+ write_zsctrl(c, RES_EOM_L);
+ write_zsreg(c, R5, c->regs[R5]|TxENAB);
+ }
+ else
+ {
+
+ /* ABUNDER off */
+ write_zsreg(c, R10, c->regs[10]);
+ write_zsctrl(c, RES_Tx_CRC);
+
+ while(c->txcount && (read_zsreg(c,R0)&Tx_BUF_EMP))
+ {
+ write_zsreg(c, R8, *c->tx_ptr++);
+ c->txcount--;
+ }
+
+ }
+ }
+ /*
+ * Since we emptied tx_skb we can ask for more
+ */
+ netif_wake_queue(c->netdevice);
+}
+
+/**
+ * z8530_tx_done - TX complete callback
+ * @c: The channel that completed a transmit.
+ *
+ * This is called when we complete a packet send. We wake the queue,
+ * start the next packet going and then free the buffer of the existing
+ * packet. This code is fairly timing sensitive.
+ *
+ * Called with the register lock held.
+ */
+
+static void z8530_tx_done(struct z8530_channel *c)
+{
+ struct sk_buff *skb;
+
+ /* Actually this can happen.*/
+ if(c->tx_skb==NULL)
+ return;
+
+ skb=c->tx_skb;
+ c->tx_skb=NULL;
+ z8530_tx_begin(c);
+ c->stats.tx_packets++;
+ c->stats.tx_bytes+=skb->len;
+ dev_kfree_skb_irq(skb);
+}
+
+/**
+ * z8530_null_rx - Discard a packet
+ * @c: The channel the packet arrived on
+ * @skb: The buffer
+ *
+ * We point the receive handler at this function when idle. Instead
+ * of syncppp processing the frames we get to throw them away.
+ */
+
+void z8530_null_rx(struct z8530_channel *c, struct sk_buff *skb)
+{
+ dev_kfree_skb_any(skb);
+}
+
+EXPORT_SYMBOL(z8530_null_rx);
+
+/**
+ * z8530_rx_done - Receive completion callback
+ * @c: The channel that completed a receive
+ *
+ * A new packet is complete. Our goal here is to get back into receive
+ * mode as fast as possible. On the Z85230 we could change to using
+ * ESCC mode, but on the older chips we have no choice. We flip to the
+ * new buffer immediately in DMA mode so that the DMA of the next
+ * frame can occur while we are copying the previous buffer to an sk_buff
+ *
+ * Called with the lock held
+ */
+
+static void z8530_rx_done(struct z8530_channel *c)
+{
+ struct sk_buff *skb;
+ int ct;
+
+ /*
+ * Is our receive engine in DMA mode
+ */
+
+ if(c->rxdma_on)
+ {
+ /*
+ * Save the ready state and the buffer currently
+ * being used as the DMA target
+ */
+
+ int ready=c->dma_ready;
+ unsigned char *rxb=c->rx_buf[c->dma_num];
+ unsigned long flags;
+
+ /*
+ * Complete this DMA. Neccessary to find the length
+ */
+
+ flags=claim_dma_lock();
+
+ disable_dma(c->rxdma);
+ clear_dma_ff(c->rxdma);
+ c->rxdma_on=0;
+ ct=c->mtu-get_dma_residue(c->rxdma);
+ if(ct<0)
+ ct=2; /* Shit happens.. */
+ c->dma_ready=0;
+
+ /*
+ * Normal case: the other slot is free, start the next DMA
+ * into it immediately.
+ */
+
+ if(ready)
+ {
+ c->dma_num^=1;
+ set_dma_mode(c->rxdma, DMA_MODE_READ|0x10);
+ set_dma_addr(c->rxdma, virt_to_bus(c->rx_buf[c->dma_num]));
+ set_dma_count(c->rxdma, c->mtu);
+ c->rxdma_on = 1;
+ enable_dma(c->rxdma);
+ /* Stop any frames that we missed the head of
+ from passing */
+ write_zsreg(c, R0, RES_Rx_CRC);
+ }
+ else
+ /* Can't occur as we dont reenable the DMA irq until
+ after the flip is done */
+ printk(KERN_WARNING "%s: DMA flip overrun!\n", c->netdevice->name);
+
+ release_dma_lock(flags);
+
+ /*
+ * Shove the old buffer into an sk_buff. We can't DMA
+ * directly into one on a PC - it might be above the 16Mb
+ * boundary. Optimisation - we could check to see if we
+ * can avoid the copy. Optimisation 2 - make the memcpy
+ * a copychecksum.
+ */
+
+ skb=dev_alloc_skb(ct);
+ if(skb==NULL)
+ {
+ c->stats.rx_dropped++;
+ printk(KERN_WARNING "%s: Memory squeeze.\n", c->netdevice->name);
+ }
+ else
+ {
+ skb_put(skb, ct);
+ memcpy(skb->data, rxb, ct);
+ c->stats.rx_packets++;
+ c->stats.rx_bytes+=ct;
+ }
+ c->dma_ready=1;
+ }
+ else
+ {
+ RT_LOCK;
+ skb=c->skb;
+
+ /*
+ * The game we play for non DMA is similar. We want to
+ * get the controller set up for the next packet as fast
+ * as possible. We potentially only have one byte + the
+ * fifo length for this. Thus we want to flip to the new
+ * buffer and then mess around copying and allocating
+ * things. For the current case it doesn't matter but
+ * if you build a system where the sync irq isnt blocked
+ * by the kernel IRQ disable then you need only block the
+ * sync IRQ for the RT_LOCK area.
+ *
+ */
+ ct=c->count;
+
+ c->skb = c->skb2;
+ c->count = 0;
+ c->max = c->mtu;
+ if(c->skb)
+ {
+ c->dptr = c->skb->data;
+ c->max = c->mtu;
+ }
+ else
+ {
+ c->count= 0;
+ c->max = 0;
+ }
+ RT_UNLOCK;
+
+ c->skb2 = dev_alloc_skb(c->mtu);
+ if(c->skb2==NULL)
+ printk(KERN_WARNING "%s: memory squeeze.\n",
+ c->netdevice->name);
+ else
+ {
+ skb_put(c->skb2,c->mtu);
+ }
+ c->stats.rx_packets++;
+ c->stats.rx_bytes+=ct;
+
+ }
+ /*
+ * If we received a frame we must now process it.
+ */
+ if(skb)
+ {
+ skb_trim(skb, ct);
+ c->rx_function(c,skb);
+ }
+ else
+ {
+ c->stats.rx_dropped++;
+ printk(KERN_ERR "%s: Lost a frame\n", c->netdevice->name);
+ }
+}
+
+/**
+ * spans_boundary - Check a packet can be ISA DMA'd
+ * @skb: The buffer to check
+ *
+ * Returns true if the buffer cross a DMA boundary on a PC. The poor
+ * thing can only DMA within a 64K block not across the edges of it.
+ */
+
+static inline int spans_boundary(struct sk_buff *skb)
+{
+ unsigned long a=(unsigned long)skb->data;
+ a^=(a+skb->len);
+ if(a&0x00010000) /* If the 64K bit is different.. */
+ return 1;
+ return 0;
+}
+
+/**
+ * z8530_queue_xmit - Queue a packet
+ * @c: The channel to use
+ * @skb: The packet to kick down the channel
+ *
+ * Queue a packet for transmission. Because we have rather
+ * hard to hit interrupt latencies for the Z85230 per packet
+ * even in DMA mode we do the flip to DMA buffer if needed here
+ * not in the IRQ.
+ *
+ * Called from the network code. The lock is not held at this
+ * point.
+ */
+
+int z8530_queue_xmit(struct z8530_channel *c, struct sk_buff *skb)
+{
+ unsigned long flags;
+
+ netif_stop_queue(c->netdevice);
+ if(c->tx_next_skb)
+ {
+ return 1;
+ }
+
+ /* PC SPECIFIC - DMA limits */
+
+ /*
+ * If we will DMA the transmit and its gone over the ISA bus
+ * limit, then copy to the flip buffer
+ */
+
+ if(c->dma_tx && ((unsigned long)(virt_to_bus(skb->data+skb->len))>=16*1024*1024 || spans_boundary(skb)))
+ {
+ /*
+ * Send the flip buffer, and flip the flippy bit.
+ * We don't care which is used when just so long as
+ * we never use the same buffer twice in a row. Since
+ * only one buffer can be going out at a time the other
+ * has to be safe.
+ */
+ c->tx_next_ptr=c->tx_dma_buf[c->tx_dma_used];
+ c->tx_dma_used^=1; /* Flip temp buffer */
+ memcpy(c->tx_next_ptr, skb->data, skb->len);
+ }
+ else
+ c->tx_next_ptr=skb->data;
+ RT_LOCK;
+ c->tx_next_skb=skb;
+ RT_UNLOCK;
+
+ spin_lock_irqsave(c->lock, flags);
+ z8530_tx_begin(c);
+ spin_unlock_irqrestore(c->lock, flags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(z8530_queue_xmit);
+
+/**
+ * z8530_get_stats - Get network statistics
+ * @c: The channel to use
+ *
+ * Get the statistics block. We keep the statistics in software as
+ * the chip doesn't do it for us.
+ *
+ * Locking is ignored here - we could lock for a copy but its
+ * not likely to be that big an issue
+ */
+
+struct net_device_stats *z8530_get_stats(struct z8530_channel *c)
+{
+ return &c->stats;
+}
+
+EXPORT_SYMBOL(z8530_get_stats);
+
+/*
+ * Module support
+ */
+static char banner[] __initdata = KERN_INFO "Generic Z85C30/Z85230 interface driver v0.02\n";
+
+static int __init z85230_init_driver(void)
+{
+ printk(banner);
+ return 0;
+}
+module_init(z85230_init_driver);
+
+static void __exit z85230_cleanup_driver(void)
+{
+}
+module_exit(z85230_cleanup_driver);
+
+MODULE_AUTHOR("Red Hat Inc.");
+MODULE_DESCRIPTION("Z85x30 synchronous driver core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wan/z85230.h b/drivers/net/wan/z85230.h
new file mode 100644
index 00000000000..77e53208045
--- /dev/null
+++ b/drivers/net/wan/z85230.h
@@ -0,0 +1,449 @@
+/*
+ * Description of Z8530 Z85C30 and Z85230 communications chips
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ * Copyright (C) 1998 Alan Cox <alan@redhat.com>
+ */
+
+#ifndef _Z8530_H
+#define _Z8530_H
+
+#include <linux/tty.h>
+#include <linux/interrupt.h>
+
+/* Conversion routines to/from brg time constants from/to bits
+ * per second.
+ */
+#define BRG_TO_BPS(brg, freq) ((freq) / 2 / ((brg) + 2))
+#define BPS_TO_BRG(bps, freq) ((((freq) + (bps)) / (2 * (bps))) - 2)
+
+/* The Zilog register set */
+
+#define FLAG 0x7e
+
+/* Write Register 0 */
+#define R0 0 /* Register selects */
+#define R1 1
+#define R2 2
+#define R3 3
+#define R4 4
+#define R5 5
+#define R6 6
+#define R7 7
+#define R8 8
+#define R9 9
+#define R10 10
+#define R11 11
+#define R12 12
+#define R13 13
+#define R14 14
+#define R15 15
+
+#define RPRIME 16 /* Indicate a prime register access on 230 */
+
+#define NULLCODE 0 /* Null Code */
+#define POINT_HIGH 0x8 /* Select upper half of registers */
+#define RES_EXT_INT 0x10 /* Reset Ext. Status Interrupts */
+#define SEND_ABORT 0x18 /* HDLC Abort */
+#define RES_RxINT_FC 0x20 /* Reset RxINT on First Character */
+#define RES_Tx_P 0x28 /* Reset TxINT Pending */
+#define ERR_RES 0x30 /* Error Reset */
+#define RES_H_IUS 0x38 /* Reset highest IUS */
+
+#define RES_Rx_CRC 0x40 /* Reset Rx CRC Checker */
+#define RES_Tx_CRC 0x80 /* Reset Tx CRC Checker */
+#define RES_EOM_L 0xC0 /* Reset EOM latch */
+
+/* Write Register 1 */
+
+#define EXT_INT_ENAB 0x1 /* Ext Int Enable */
+#define TxINT_ENAB 0x2 /* Tx Int Enable */
+#define PAR_SPEC 0x4 /* Parity is special condition */
+
+#define RxINT_DISAB 0 /* Rx Int Disable */
+#define RxINT_FCERR 0x8 /* Rx Int on First Character Only or Error */
+#define INT_ALL_Rx 0x10 /* Int on all Rx Characters or error */
+#define INT_ERR_Rx 0x18 /* Int on error only */
+
+#define WT_RDY_RT 0x20 /* Wait/Ready on R/T */
+#define WT_FN_RDYFN 0x40 /* Wait/FN/Ready FN */
+#define WT_RDY_ENAB 0x80 /* Wait/Ready Enable */
+
+/* Write Register #2 (Interrupt Vector) */
+
+/* Write Register 3 */
+
+#define RxENABLE 0x1 /* Rx Enable */
+#define SYNC_L_INH 0x2 /* Sync Character Load Inhibit */
+#define ADD_SM 0x4 /* Address Search Mode (SDLC) */
+#define RxCRC_ENAB 0x8 /* Rx CRC Enable */
+#define ENT_HM 0x10 /* Enter Hunt Mode */
+#define AUTO_ENAB 0x20 /* Auto Enables */
+#define Rx5 0x0 /* Rx 5 Bits/Character */
+#define Rx7 0x40 /* Rx 7 Bits/Character */
+#define Rx6 0x80 /* Rx 6 Bits/Character */
+#define Rx8 0xc0 /* Rx 8 Bits/Character */
+
+/* Write Register 4 */
+
+#define PAR_ENA 0x1 /* Parity Enable */
+#define PAR_EVEN 0x2 /* Parity Even/Odd* */
+
+#define SYNC_ENAB 0 /* Sync Modes Enable */
+#define SB1 0x4 /* 1 stop bit/char */
+#define SB15 0x8 /* 1.5 stop bits/char */
+#define SB2 0xc /* 2 stop bits/char */
+
+#define MONSYNC 0 /* 8 Bit Sync character */
+#define BISYNC 0x10 /* 16 bit sync character */
+#define SDLC 0x20 /* SDLC Mode (01111110 Sync Flag) */
+#define EXTSYNC 0x30 /* External Sync Mode */
+
+#define X1CLK 0x0 /* x1 clock mode */
+#define X16CLK 0x40 /* x16 clock mode */
+#define X32CLK 0x80 /* x32 clock mode */
+#define X64CLK 0xC0 /* x64 clock mode */
+
+/* Write Register 5 */
+
+#define TxCRC_ENAB 0x1 /* Tx CRC Enable */
+#define RTS 0x2 /* RTS */
+#define SDLC_CRC 0x4 /* SDLC/CRC-16 */
+#define TxENAB 0x8 /* Tx Enable */
+#define SND_BRK 0x10 /* Send Break */
+#define Tx5 0x0 /* Tx 5 bits (or less)/character */
+#define Tx7 0x20 /* Tx 7 bits/character */
+#define Tx6 0x40 /* Tx 6 bits/character */
+#define Tx8 0x60 /* Tx 8 bits/character */
+#define DTR 0x80 /* DTR */
+
+/* Write Register 6 (Sync bits 0-7/SDLC Address Field) */
+
+/* Write Register 7 (Sync bits 8-15/SDLC 01111110) */
+
+/* Write Register 8 (transmit buffer) */
+
+/* Write Register 9 (Master interrupt control) */
+#define VIS 1 /* Vector Includes Status */
+#define NV 2 /* No Vector */
+#define DLC 4 /* Disable Lower Chain */
+#define MIE 8 /* Master Interrupt Enable */
+#define STATHI 0x10 /* Status high */
+#define NORESET 0 /* No reset on write to R9 */
+#define CHRB 0x40 /* Reset channel B */
+#define CHRA 0x80 /* Reset channel A */
+#define FHWRES 0xc0 /* Force hardware reset */
+
+/* Write Register 10 (misc control bits) */
+#define BIT6 1 /* 6 bit/8bit sync */
+#define LOOPMODE 2 /* SDLC Loop mode */
+#define ABUNDER 4 /* Abort/flag on SDLC xmit underrun */
+#define MARKIDLE 8 /* Mark/flag on idle */
+#define GAOP 0x10 /* Go active on poll */
+#define NRZ 0 /* NRZ mode */
+#define NRZI 0x20 /* NRZI mode */
+#define FM1 0x40 /* FM1 (transition = 1) */
+#define FM0 0x60 /* FM0 (transition = 0) */
+#define CRCPS 0x80 /* CRC Preset I/O */
+
+/* Write Register 11 (Clock Mode control) */
+#define TRxCXT 0 /* TRxC = Xtal output */
+#define TRxCTC 1 /* TRxC = Transmit clock */
+#define TRxCBR 2 /* TRxC = BR Generator Output */
+#define TRxCDP 3 /* TRxC = DPLL output */
+#define TRxCOI 4 /* TRxC O/I */
+#define TCRTxCP 0 /* Transmit clock = RTxC pin */
+#define TCTRxCP 8 /* Transmit clock = TRxC pin */
+#define TCBR 0x10 /* Transmit clock = BR Generator output */
+#define TCDPLL 0x18 /* Transmit clock = DPLL output */
+#define RCRTxCP 0 /* Receive clock = RTxC pin */
+#define RCTRxCP 0x20 /* Receive clock = TRxC pin */
+#define RCBR 0x40 /* Receive clock = BR Generator output */
+#define RCDPLL 0x60 /* Receive clock = DPLL output */
+#define RTxCX 0x80 /* RTxC Xtal/No Xtal */
+
+/* Write Register 12 (lower byte of baud rate generator time constant) */
+
+/* Write Register 13 (upper byte of baud rate generator time constant) */
+
+/* Write Register 14 (Misc control bits) */
+#define BRENABL 1 /* Baud rate generator enable */
+#define BRSRC 2 /* Baud rate generator source */
+#define DTRREQ 4 /* DTR/Request function */
+#define AUTOECHO 8 /* Auto Echo */
+#define LOOPBAK 0x10 /* Local loopback */
+#define SEARCH 0x20 /* Enter search mode */
+#define RMC 0x40 /* Reset missing clock */
+#define DISDPLL 0x60 /* Disable DPLL */
+#define SSBR 0x80 /* Set DPLL source = BR generator */
+#define SSRTxC 0xa0 /* Set DPLL source = RTxC */
+#define SFMM 0xc0 /* Set FM mode */
+#define SNRZI 0xe0 /* Set NRZI mode */
+
+/* Write Register 15 (external/status interrupt control) */
+#define PRIME 1 /* R5' etc register access (Z85C30/230 only) */
+#define ZCIE 2 /* Zero count IE */
+#define FIFOE 4 /* Z85230 only */
+#define DCDIE 8 /* DCD IE */
+#define SYNCIE 0x10 /* Sync/hunt IE */
+#define CTSIE 0x20 /* CTS IE */
+#define TxUIE 0x40 /* Tx Underrun/EOM IE */
+#define BRKIE 0x80 /* Break/Abort IE */
+
+
+/* Read Register 0 */
+#define Rx_CH_AV 0x1 /* Rx Character Available */
+#define ZCOUNT 0x2 /* Zero count */
+#define Tx_BUF_EMP 0x4 /* Tx Buffer empty */
+#define DCD 0x8 /* DCD */
+#define SYNC_HUNT 0x10 /* Sync/hunt */
+#define CTS 0x20 /* CTS */
+#define TxEOM 0x40 /* Tx underrun */
+#define BRK_ABRT 0x80 /* Break/Abort */
+
+/* Read Register 1 */
+#define ALL_SNT 0x1 /* All sent */
+/* Residue Data for 8 Rx bits/char programmed */
+#define RES3 0x8 /* 0/3 */
+#define RES4 0x4 /* 0/4 */
+#define RES5 0xc /* 0/5 */
+#define RES6 0x2 /* 0/6 */
+#define RES7 0xa /* 0/7 */
+#define RES8 0x6 /* 0/8 */
+#define RES18 0xe /* 1/8 */
+#define RES28 0x0 /* 2/8 */
+/* Special Rx Condition Interrupts */
+#define PAR_ERR 0x10 /* Parity error */
+#define Rx_OVR 0x20 /* Rx Overrun Error */
+#define CRC_ERR 0x40 /* CRC/Framing Error */
+#define END_FR 0x80 /* End of Frame (SDLC) */
+
+/* Read Register 2 (channel b only) - Interrupt vector */
+
+/* Read Register 3 (interrupt pending register) ch a only */
+#define CHBEXT 0x1 /* Channel B Ext/Stat IP */
+#define CHBTxIP 0x2 /* Channel B Tx IP */
+#define CHBRxIP 0x4 /* Channel B Rx IP */
+#define CHAEXT 0x8 /* Channel A Ext/Stat IP */
+#define CHATxIP 0x10 /* Channel A Tx IP */
+#define CHARxIP 0x20 /* Channel A Rx IP */
+
+/* Read Register 8 (receive data register) */
+
+/* Read Register 10 (misc status bits) */
+#define ONLOOP 2 /* On loop */
+#define LOOPSEND 0x10 /* Loop sending */
+#define CLK2MIS 0x40 /* Two clocks missing */
+#define CLK1MIS 0x80 /* One clock missing */
+
+/* Read Register 12 (lower byte of baud rate generator constant) */
+
+/* Read Register 13 (upper byte of baud rate generator constant) */
+
+/* Read Register 15 (value of WR 15) */
+
+
+/*
+ * Interrupt handling functions for this SCC
+ */
+
+struct z8530_channel;
+
+struct z8530_irqhandler
+{
+ void (*rx)(struct z8530_channel *);
+ void (*tx)(struct z8530_channel *);
+ void (*status)(struct z8530_channel *);
+};
+
+/*
+ * A channel of the Z8530
+ */
+
+struct z8530_channel
+{
+ struct z8530_irqhandler *irqs; /* IRQ handlers */
+ /*
+ * Synchronous
+ */
+ u16 count; /* Buyes received */
+ u16 max; /* Most we can receive this frame */
+ u16 mtu; /* MTU of the device */
+ u8 *dptr; /* Pointer into rx buffer */
+ struct sk_buff *skb; /* Buffer dptr points into */
+ struct sk_buff *skb2; /* Pending buffer */
+ u8 status; /* Current DCD */
+ u8 dcdcheck; /* which bit to check for line */
+ u8 sync; /* Set if in sync mode */
+
+ u8 regs[32]; /* Register map for the chip */
+ u8 pendregs[32]; /* Pending register values */
+
+ struct sk_buff *tx_skb; /* Buffer being transmitted */
+ struct sk_buff *tx_next_skb; /* Next transmit buffer */
+ u8 *tx_ptr; /* Byte pointer into the buffer */
+ u8 *tx_next_ptr; /* Next pointer to use */
+ u8 *tx_dma_buf[2]; /* TX flip buffers for DMA */
+ u8 tx_dma_used; /* Flip buffer usage toggler */
+ u16 txcount; /* Count of bytes to transmit */
+
+ void (*rx_function)(struct z8530_channel *, struct sk_buff *);
+
+ /*
+ * Sync DMA
+ */
+
+ u8 rxdma; /* DMA channels */
+ u8 txdma;
+ u8 rxdma_on; /* DMA active if flag set */
+ u8 txdma_on;
+ u8 dma_num; /* Buffer we are DMAing into */
+ u8 dma_ready; /* Is the other buffer free */
+ u8 dma_tx; /* TX is to use DMA */
+ u8 *rx_buf[2]; /* The flip buffers */
+
+ /*
+ * System
+ */
+
+ struct z8530_dev *dev; /* Z85230 chip instance we are from */
+ unsigned long ctrlio; /* I/O ports */
+ unsigned long dataio;
+
+ /*
+ * For PC we encode this way.
+ */
+#define Z8530_PORT_SLEEP 0x80000000
+#define Z8530_PORT_OF(x) ((x)&0xFFFF)
+
+ u32 rx_overrun; /* Overruns - not done yet */
+ u32 rx_crc_err;
+
+ /*
+ * Bound device pointers
+ */
+
+ void *private; /* For our owner */
+ struct net_device *netdevice; /* Network layer device */
+ struct net_device_stats stats; /* Network layer statistics */
+
+ /*
+ * Async features
+ */
+
+ struct tty_struct *tty; /* Attached terminal */
+ int line; /* Minor number */
+ wait_queue_head_t open_wait; /* Tasks waiting to open */
+ wait_queue_head_t close_wait; /* and for close to end */
+ unsigned long event; /* Pending events */
+ int fdcount; /* # of fd on device */
+ int blocked_open; /* # of blocked opens */
+ int x_char; /* XON/XOF char */
+ unsigned char *xmit_buf; /* Transmit pointer */
+ int xmit_head; /* Transmit ring */
+ int xmit_tail;
+ int xmit_cnt;
+ int flags;
+ int timeout;
+ int xmit_fifo_size; /* Transmit FIFO info */
+
+ int close_delay; /* Do we wait for drain on close ? */
+ unsigned short closing_wait;
+
+ /* We need to know the current clock divisor
+ * to read the bps rate the chip has currently
+ * loaded.
+ */
+
+ unsigned char clk_divisor; /* May be 1, 16, 32, or 64 */
+ int zs_baud;
+
+ int magic;
+ int baud_base; /* Baud parameters */
+ int custom_divisor;
+
+
+ unsigned char tx_active; /* character is being xmitted */
+ unsigned char tx_stopped; /* output is suspended */
+
+ spinlock_t *lock; /* Devicr lock */
+};
+
+/*
+ * Each Z853x0 device.
+ */
+
+struct z8530_dev
+{
+ char *name; /* Device instance name */
+ struct z8530_channel chanA; /* SCC channel A */
+ struct z8530_channel chanB; /* SCC channel B */
+ int type;
+#define Z8530 0 /* NMOS dinosaur */
+#define Z85C30 1 /* CMOS - better */
+#define Z85230 2 /* CMOS with real FIFO */
+ int irq; /* Interrupt for the device */
+ int active; /* Soft interrupt enable - the Mac doesn't
+ always have a hard disable on its 8530s... */
+ spinlock_t lock;
+};
+
+
+/*
+ * Functions
+ */
+
+extern u8 z8530_dead_port[];
+extern u8 z8530_hdlc_kilostream_85230[];
+extern u8 z8530_hdlc_kilostream[];
+extern irqreturn_t z8530_interrupt(int, void *, struct pt_regs *);
+extern void z8530_describe(struct z8530_dev *, char *mapping, unsigned long io);
+extern int z8530_init(struct z8530_dev *);
+extern int z8530_shutdown(struct z8530_dev *);
+extern int z8530_sync_open(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_close(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_dma_open(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_dma_close(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_txdma_open(struct net_device *, struct z8530_channel *);
+extern int z8530_sync_txdma_close(struct net_device *, struct z8530_channel *);
+extern int z8530_channel_load(struct z8530_channel *, u8 *);
+extern int z8530_queue_xmit(struct z8530_channel *c, struct sk_buff *skb);
+extern struct net_device_stats *z8530_get_stats(struct z8530_channel *c);
+extern void z8530_null_rx(struct z8530_channel *c, struct sk_buff *skb);
+
+
+/*
+ * Standard interrupt vector sets
+ */
+
+extern struct z8530_irqhandler z8530_sync, z8530_async, z8530_nop;
+
+/*
+ * Asynchronous Interfacing
+ */
+
+#define SERIAL_MAGIC 0x5301
+
+/*
+ * The size of the serial xmit buffer is 1 page, or 4096 bytes
+ */
+
+#define SERIAL_XMIT_SIZE 4096
+#define WAKEUP_CHARS 256
+
+/*
+ * Events are used to schedule things to happen at timer-interrupt
+ * time, instead of at rs interrupt time.
+ */
+#define RS_EVENT_WRITE_WAKEUP 0
+
+/* Internal flags used only by kernel/chr_drv/serial.c */
+#define ZILOG_INITIALIZED 0x80000000 /* Serial port was initialized */
+#define ZILOG_CALLOUT_ACTIVE 0x40000000 /* Call out device is active */
+#define ZILOG_NORMAL_ACTIVE 0x20000000 /* Normal device is active */
+#define ZILOG_BOOT_AUTOCONF 0x10000000 /* Autoconfigure port on bootup */
+#define ZILOG_CLOSING 0x08000000 /* Serial port is closing */
+#define ZILOG_CTS_FLOW 0x04000000 /* Do CTS flow control */
+#define ZILOG_CHECK_CD 0x02000000 /* i.e., CLOCAL */
+
+#endif /* !(_Z8530_H) */