diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/net/wan |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/net/wan')
63 files changed, 64469 insertions, 0 deletions
diff --git a/drivers/net/wan/Kconfig b/drivers/net/wan/Kconfig new file mode 100644 index 000000000000..35791934a602 --- /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 000000000000..ce6c56b903e7 --- /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 000000000000..43d854ace233 --- /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 000000000000..921a573372e9 --- /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 000000000000..028f3d96b971 --- /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 000000000000..6e74af62ca08 --- /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 000000000000..7b48064364dc --- /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 000000000000..5b48cd8568f5 --- /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 000000000000..6e1ec5bf22fc --- /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 000000000000..520a77a798e2 --- /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 000000000000..7575b799ce53 --- /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 000000000000..d871dafa87a1 --- /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 000000000000..3839662ff201 --- /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 000000000000..96567c2dc4db --- /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 000000000000..d3743321a977 --- /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 000000000000..c1b6896d7007 --- /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 000000000000..7f450b51a6cb --- /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 000000000000..6ed064cb4469 --- /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 000000000000..7cd6195a2e46 --- /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 000000000000..c41fb70b6929 --- /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 000000000000..b1285cc8fee6 --- /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 000000000000..07e5eef1fe0f --- /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 000000000000..7db1d1d0bb34 --- /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 000000000000..7f2e3653c5e5 --- /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 000000000000..dabdcfed4efd --- /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 000000000000..882e58c1bfd7 --- /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 000000000000..9dccd9546a17 --- /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 000000000000..cf3563859bf3 --- /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 000000000000..57dd861cd3db --- /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 000000000000..15e545f66cd7 --- /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 000000000000..f55ce76b00ed --- /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 000000000000..ddcc00403563 --- /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 000000000000..f3b1df9e2cdb --- /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 000000000000..74876c0073e8 --- /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 000000000000..080a55773349 --- /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 000000000000..6d003a39bfad --- /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 000000000000..cd32751b64eb --- /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 000000000000..01ed23ca76c7 --- /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 000000000000..73401b0f0151 --- /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 000000000000..d67be2587d4d --- /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 000000000000..29f84ad08730 --- /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 000000000000..8dea07b47999 --- /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 000000000000..db2c798ba89e --- /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 000000000000..27715e70f28b --- /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 000000000000..3ac9a45b20fa --- /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 000000000000..afbe0024e3e1 --- /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 000000000000..2efccb0554c0 --- /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 000000000000..5e3124856eb0 --- /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 000000000000..1761cb68ab48 --- /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 000000000000..3a93d2fd4fbf --- /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 000000000000..c8bc6da57a41 --- /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 000000000000..74e151acef3e --- /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 000000000000..5380ddfcd7d5 --- /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 000000000000..84b65c60c799 --- /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 000000000000..6aa6987d96cb --- /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 000000000000..1e7b47704ad9 --- /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 000000000000..3f86558f8a6b --- /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 000000000000..73aae2bf2f1c --- /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 000000000000..73da688f943b --- /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 000000000000..8c5cfcb55826 --- /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 000000000000..41770200ceb6 --- /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 000000000000..caa48f12fd0f --- /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 000000000000..77e53208045f --- /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) */ |