diff options
Diffstat (limited to 'drivers/net/wan')
53 files changed, 33125 insertions, 0 deletions
diff --git a/drivers/net/wan/.gitignore b/drivers/net/wan/.gitignore new file mode 100644 index 000000000..dae3ea6bb --- /dev/null +++ b/drivers/net/wan/.gitignore @@ -0,0 +1 @@ +wanxlfw.inc diff --git a/drivers/net/wan/Kconfig b/drivers/net/wan/Kconfig new file mode 100644 index 000000000..17ed5107b --- /dev/null +++ b/drivers/net/wan/Kconfig @@ -0,0 +1,442 @@ +# +# wan devices configuration +# + +menuconfig 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. + +if WAN + +# 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 ISA && m && ISA_DMA_API && INET && HDLC && VIRT_TO_BUS + 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 ISA && m && ISA_DMA_API && HDLC && VIRT_TO_BUS + ---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. + +# +# Lan Media's board. Currently 1000, 1200, 5200, 5245 +# +config LANMEDIA + tristate "LanMedia Corp. SSI/V.35, T1/E1, HSSI, T3 boards" + depends on PCI && VIRT_TO_BUS && HDLC + ---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 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 ISA && m && ISA_DMA_API && INET && HDLC && VIRT_TO_BUS + 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. + +# Generic HDLC +config HDLC + tristate "Generic HDLC layer" + 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 + tristate "Raw HDLC support" + depends on HDLC + help + Generic HDLC driver supporting raw HDLC over WAN connections. + + If unsure, say N. + +config HDLC_RAW_ETH + tristate "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 + tristate "Cisco HDLC support" + depends on HDLC + help + Generic HDLC driver supporting Cisco HDLC over WAN connections. + + If unsure, say N. + +config HDLC_FR + tristate "Frame Relay support" + depends on HDLC + help + Generic HDLC driver supporting Frame Relay over WAN connections. + + If unsure, say N. + +config HDLC_PPP + tristate "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 + tristate "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 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 m68k toolchains and hexdump programs. + + You should never need this option, say N. + +config PC300TOO + tristate "Cyclades PC300 RSV/X21 alternative support" + depends on HDLC && PCI + help + Alternative driver for PC300 RSV/X21 PCI cards made by + Cyclades, 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 pc300too. + + If unsure, say N here. + +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 a file + in /etc/modprobe.d/ 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 DSCC4 + tristate "Etinc PCISYNC serial board support" + depends on HDLC && 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 FSL_UCC_HDLC + tristate "Freescale QUICC Engine HDLC support" + depends on HDLC + depends on QUICC_ENGINE + help + Driver for Freescale QUICC Engine HDLC controller. The driver + supports HDLC in NMSI and TDM mode. + + To compile this driver as a module, choose M here: the + module will be called fsl_ucc_hdlc. + +config SLIC_DS26522 + tristate "Slic Maxim ds26522 card support" + depends on SPI + depends on FSL_SOC || ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST + select BITREVERSE + help + This module initializes and configures the slic maxim card + in T1 or E1 mode. + + To compile this driver as a module, choose M here: the + module will be called slic_ds26522. + +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. + +config IXP4XX_HSS + tristate "Intel IXP4xx HSS (synchronous serial port) support" + depends on HDLC && ARM && ARCH_IXP4XX && IXP4XX_NPE && IXP4XX_QMGR + help + Say Y here if you want to use built-in HSS ports + on IXP4xx processor. + +config DLCI + tristate "Frame Relay DLCI support" + ---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_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. + +# X.25 network drivers +config LAPBETHER + tristate "LAPB over Ethernet driver" + depends on 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" + depends on LAPB && X25 && TTY + ---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 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. + +endif # WAN diff --git a/drivers/net/wan/Makefile b/drivers/net/wan/Makefile new file mode 100644 index 000000000..0500282e1 --- /dev/null +++ b/drivers/net/wan/Makefile @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Linux network (wan) device drivers. +# +# 3 Aug 2000, Christoph Hellwig <hch@infradead.org> +# Rewritten to use lists instead of if-statements. +# + +obj-$(CONFIG_HDLC) += hdlc.o +obj-$(CONFIG_HDLC_RAW) += hdlc_raw.o +obj-$(CONFIG_HDLC_RAW_ETH) += hdlc_raw_eth.o +obj-$(CONFIG_HDLC_CISCO) += hdlc_cisco.o +obj-$(CONFIG_HDLC_FR) += hdlc_fr.o +obj-$(CONFIG_HDLC_PPP) += hdlc_ppp.o +obj-$(CONFIG_HDLC_X25) += hdlc_x25.o + +obj-$(CONFIG_HOSTESS_SV11) += z85230.o hostess_sv11.o +obj-$(CONFIG_SEALEVEL_4021) += z85230.o sealevel.o +obj-$(CONFIG_COSA) += cosa.o +obj-$(CONFIG_FARSYNC) += farsync.o +obj-$(CONFIG_DSCC4) += dscc4.o +obj-$(CONFIG_X25_ASY) += x25_asy.o + +obj-$(CONFIG_LANMEDIA) += lmc/ + +obj-$(CONFIG_DLCI) += dlci.o +obj-$(CONFIG_SDLA) += sdla.o +obj-$(CONFIG_LAPBETHER) += lapbether.o +obj-$(CONFIG_SBNI) += sbni.o +obj-$(CONFIG_N2) += n2.o +obj-$(CONFIG_C101) += c101.o +obj-$(CONFIG_WANXL) += wanxl.o +obj-$(CONFIG_PCI200SYN) += pci200syn.o +obj-$(CONFIG_PC300TOO) += pc300too.o +obj-$(CONFIG_IXP4XX_HSS) += ixp4xx_hss.o +obj-$(CONFIG_FSL_UCC_HDLC) += fsl_ucc_hdlc.o +obj-$(CONFIG_SLIC_DS26522) += slic_ds26522.o + +clean-files := wanxlfw.inc +$(obj)/wanxl.o: $(obj)/wanxlfw.inc + +ifeq ($(CONFIG_WANXL_BUILD_FIRMWARE),y) +ifeq ($(ARCH),m68k) + M68KCC = $(CC) + M68KLD = $(LD) +else + M68KCC = $(CROSS_COMPILE_M68K)gcc + M68KLD = $(CROSS_COMPILE_M68K)ld +endif + +quiet_cmd_build_wanxlfw = BLD FW $@ + cmd_build_wanxlfw = \ + $(M68KCC) -D__ASSEMBLY__ -Wp,-MD,$(depfile) -I$(srctree)/include/uapi -c -o $(obj)/wanxlfw.o $<; \ + $(M68KLD) --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 const 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 000000000..2371e078a --- /dev/null +++ b/drivers/net/wan/c101.c @@ -0,0 +1,453 @@ +/* + * 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://www.kernel.org/pub/linux/utils/net/hdlc/> + * + * Sources of information: + * Hitachi HD64570 SCA User's Manual + * Moxa C101 User's Manual + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/capability.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 "hd64570.c" + + +static inline void set_carrier(port_t *port) +{ + if (!(sca_in(MSCI1_OFFSET + ST3, port) & ST3_DCD)) + netif_carrier_on(port_to_dev(port)); + else + netif_carrier_off(port_to_dev(port)); +} + + +static void sca_msci_intr(port_t *port) +{ + u8 stat = sca_in(MSCI0_OFFSET + ST1, port); /* read MSCI ST1 status */ + + /* Reset MSCI TX underrun and CDCD (ignored) status bit */ + sca_out(stat & (ST1_UDRN | ST1_CDCD), MSCI0_OFFSET + ST1, port); + + if (stat & ST1_UDRN) { + /* TX Underrun error detected */ + port_to_dev(port)->stats.tx_errors++; + port_to_dev(port)->stats.tx_fifo_errors++; + } + + stat = sca_in(MSCI1_OFFSET + ST1, port); /* read MSCI1 ST1 status */ + /* Reset MSCI CDCD status bit - uses ch#2 DCD input */ + sca_out(stat & ST1_CDCD, MSCI1_OFFSET + ST1, port); + + if (stat & ST1_CDCD) + set_carrier(port); +} + + +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); + + set_carrier(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 const struct net_device_ops c101_ops = { + .ndo_open = c101_open, + .ndo_stop = c101_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = c101_ioctl, +}; + +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 */ { + pr_err("invalid IRQ value\n"); + return -ENODEV; + } + + if (winbase < 0xC0000 || winbase > 0xDFFFF || (winbase & 0x3FFF) !=0) { + pr_err("invalid RAM value\n"); + return -ENODEV; + } + + card = kzalloc(sizeof(card_t), GFP_KERNEL); + if (card == NULL) + return -ENOBUFS; + + card->dev = alloc_hdlcdev(card); + if (!card->dev) { + pr_err("unable to allocate memory\n"); + kfree(card); + return -ENOBUFS; + } + + if (request_irq(irq, sca_intr, 0, devname, card)) { + pr_err("could not allocate IRQ\n"); + c101_destroy_card(card); + return -EBUSY; + } + card->irq = irq; + + if (!request_mem_region(winbase, C101_MAPPED_RAM_SIZE, devname)) { + pr_err("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) { + pr_err("could not map I/O address\n"); + c101_destroy_card(card); + return -EFAULT; + } + + 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); + dev->irq = irq; + dev->mem_start = winbase; + dev->mem_end = winbase + C101_MAPPED_RAM_SIZE - 1; + dev->tx_queue_len = 50; + dev->netdev_ops = &c101_ops; + hdlc->attach = sca_attach; + hdlc->xmit = sca_xmit; + card->settings.clock_type = CLOCK_EXT; + + result = register_hdlc_device(dev); + if (result) { + pr_warn("unable to register hdlc device\n"); + c101_destroy_card(card); + return result; + } + + sca_init_port(card); /* Set up C101 memory */ + set_carrier(card); + + netdev_info(dev, "Moxa C101 on IRQ%u, using %u TX + %u RX packets rings\n", + 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 + pr_info("no card initialized\n"); +#endif + return -EINVAL; /* no parameters specified, abort */ + } + + pr_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 : -EINVAL; + }while(*hw++ == ':'); + + pr_err("invalid hardware parameters\n"); + return first_card ? 0 : -EINVAL; +} + + +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); +MODULE_PARM_DESC(hw, "irq,ram:irq,..."); diff --git a/drivers/net/wan/cosa.c b/drivers/net/wan/cosa.c new file mode 100644 index 000000000..b7bfc0caa --- /dev/null +++ b/drivers/net/wan/cosa.c @@ -0,0 +1,2051 @@ +/* $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> + * Generic HDLC port Copyright (C) 2008 Krzysztof Halasa <khc@pm.waw.pl> + * + * 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 two modes (character or network device). + * + * 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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/hdlc.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#undef COSA_SLOW_IO /* for testing purposes only */ + +#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 { + 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 mutex rlock; + struct semaphore wsem; + char *rxdata; + int rxsize; + wait_queue_head_t txwaitq, rxwaitq; + int tx_status, rx_status; + + /* generic HDLC device parts */ + struct net_device *netdev; + struct sk_buff *rx_skb, *tx_skb; +}; + +/* 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. */ + unsigned long rxbitmap, txbitmap;/* Bitmap of channels who are willing to send/receive data */ + unsigned long 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 DEFINE_MUTEX(cosa_chardev_mutex); +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 *cosa_class; + +#ifdef MODULE +module_param_hw_array(io, int, ioport, NULL, 0); +MODULE_PARM_DESC(io, "The I/O bases of the COSA or SRP cards"); +module_param_hw_array(irq, int, irq, NULL, 0); +MODULE_PARM_DESC(irq, "The IRQ lines of the COSA or SRP cards"); +module_param_hw_array(dma, int, dma, 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); + +/* Network device stuff */ +static int cosa_net_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity); +static int cosa_net_open(struct net_device *d); +static int cosa_net_close(struct net_device *d); +static void cosa_net_timeout(struct net_device *d); +static netdev_tx_t cosa_net_tx(struct sk_buff *skb, struct net_device *d); +static char *cosa_net_setup_rx(struct channel_data *channel, int size); +static int cosa_net_rx_done(struct channel_data *channel); +static int cosa_net_tx_done(struct channel_data *channel, int size); +static int cosa_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); + +/* Character device */ +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 long cosa_chardev_ioctl(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 const struct file_operations cosa_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = cosa_read, + .write = cosa_write, + .poll = cosa_poll, + .unlocked_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); + +/* Auxiliary 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); + +/* 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 + +static inline struct channel_data* dev_to_chan(struct net_device *dev) +{ + return (struct channel_data *)dev_to_hdlc(dev)->priv; +} + +/* ---------- Initialization stuff ---------- */ + +static int __init cosa_init(void) +{ + int i, err = 0; + + if (cosa_major > 0) { + if (register_chrdev(cosa_major, "cosa", &cosa_fops)) { + pr_warn("unable to get major %d\n", cosa_major); + err = -EIO; + goto out; + } + } else { + if (!(cosa_major=register_chrdev(0, "cosa", &cosa_fops))) { + pr_warn("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) { + pr_warn("no devices found\n"); + unregister_chrdev(cosa_major, "cosa"); + err = -ENODEV; + goto out; + } + cosa_class = class_create(THIS_MODULE, "cosa"); + if (IS_ERR(cosa_class)) { + err = PTR_ERR(cosa_class); + goto out_chrdev; + } + for (i = 0; i < nr_cards; i++) + device_create(cosa_class, NULL, MKDEV(cosa_major, i), NULL, + "cosa%d", i); + 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; + + for (i = 0; i < nr_cards; i++) + device_destroy(cosa_class, MKDEV(cosa_major, i)); + class_destroy(cosa_class); + + 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 */ + unregister_hdlc_device(cosa->chan[i].netdev); + free_netdev(cosa->chan[i].netdev); + } + /* 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); + +static const struct net_device_ops cosa_ops = { + .ndo_open = cosa_net_open, + .ndo_stop = cosa_net_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = cosa_net_ioctl, + .ndo_tx_timeout = cosa_net_timeout, +}; + +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)) { + pr_info("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) { + pr_info("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) { + pr_info("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)) { + pr_info("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 "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 + pr_info("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 "changing name at 0x%x failed.\n", base); + return -1; + } + + /* Now do IRQ autoprobe */ + if (irq < 0) { + unsigned long irqs; +/* pr_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(msecs_to_jiffies(300)); + 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) { + pr_info("multiple interrupts obtained (%d, board at 0x%x)\n", + irq, cosa->datareg); + err = -1; + goto err_out; + } + if (irq == 0) { + pr_info("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 = kcalloc(cosa->nchannels, sizeof(struct channel_data), GFP_KERNEL); + if (!cosa->chan) { + err = -ENOMEM; + goto err_out3; + } + + for (i = 0; i < cosa->nchannels; i++) { + struct channel_data *chan = &cosa->chan[i]; + + chan->cosa = cosa; + chan->num = i; + sprintf(chan->name, "cosa%dc%d", chan->cosa->num, i); + + /* Initialize the chardev data structures */ + mutex_init(&chan->rlock); + sema_init(&chan->wsem, 1); + + /* Register the network interface */ + if (!(chan->netdev = alloc_hdlcdev(chan))) { + pr_warn("%s: alloc_hdlcdev failed\n", chan->name); + err = -ENOMEM; + goto err_hdlcdev; + } + dev_to_hdlc(chan->netdev)->attach = cosa_net_attach; + dev_to_hdlc(chan->netdev)->xmit = cosa_net_tx; + chan->netdev->netdev_ops = &cosa_ops; + chan->netdev->watchdog_timeo = TX_TIMEOUT; + chan->netdev->base_addr = chan->cosa->datareg; + chan->netdev->irq = chan->cosa->irq; + chan->netdev->dma = chan->cosa->dma; + err = register_hdlc_device(chan->netdev); + if (err) { + netdev_warn(chan->netdev, + "register_hdlc_device() failed\n"); + free_netdev(chan->netdev); + goto err_hdlcdev; + } + } + + pr_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_hdlcdev: + while (i-- > 0) { + unregister_hdlc_device(cosa->chan[i].netdev); + free_netdev(cosa->chan[i].netdev); + } + kfree(cosa->chan); +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); + pr_notice("cosa%d: allocating resources failed\n", cosa->num); + return err; +} + + +/*---------- network device ---------- */ + +static int cosa_net_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + if (encoding == ENCODING_NRZ && parity == PARITY_CRC16_PR1_CCITT) + return 0; + return -EINVAL; +} + +static int cosa_net_open(struct net_device *dev) +{ + struct channel_data *chan = dev_to_chan(dev); + int err; + unsigned long flags; + + if (!(chan->cosa->firmware_status & COSA_FW_START)) { + pr_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) { + pr_warn("%s: cosa_net_open called with usage count %d\n", + chan->name, chan->usage); + spin_unlock_irqrestore(&chan->cosa->lock, flags); + return -EBUSY; + } + chan->setup_rx = cosa_net_setup_rx; + chan->tx_done = cosa_net_tx_done; + chan->rx_done = cosa_net_rx_done; + chan->usage = -1; + chan->cosa->usage++; + spin_unlock_irqrestore(&chan->cosa->lock, flags); + + err = hdlc_open(dev); + 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(dev); + cosa_enable_rx(chan); + return 0; +} + +static netdev_tx_t cosa_net_tx(struct sk_buff *skb, + struct net_device *dev) +{ + struct channel_data *chan = dev_to_chan(dev); + + netif_stop_queue(dev); + + chan->tx_skb = skb; + cosa_start_tx(chan, skb->data, skb->len); + return NETDEV_TX_OK; +} + +static void cosa_net_timeout(struct net_device *dev) +{ + struct channel_data *chan = dev_to_chan(dev); + + if (test_bit(RXBIT, &chan->cosa->rxtx)) { + chan->netdev->stats.rx_errors++; + chan->netdev->stats.rx_missed_errors++; + } else { + chan->netdev->stats.tx_errors++; + chan->netdev->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_net_close(struct net_device *dev) +{ + struct channel_data *chan = dev_to_chan(dev); + unsigned long flags; + + netif_stop_queue(dev); + hdlc_close(dev); + 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 *cosa_net_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. + */ + kfree_skb(chan->rx_skb); + chan->rx_skb = dev_alloc_skb(size); + if (chan->rx_skb == NULL) { + pr_notice("%s: Memory squeeze, dropping packet\n", chan->name); + chan->netdev->stats.rx_dropped++; + return NULL; + } + netif_trans_update(chan->netdev); + return skb_put(chan->rx_skb, size); +} + +static int cosa_net_rx_done(struct channel_data *chan) +{ + if (!chan->rx_skb) { + pr_warn("%s: rx_done with empty skb!\n", chan->name); + chan->netdev->stats.rx_errors++; + chan->netdev->stats.rx_frame_errors++; + return 0; + } + chan->rx_skb->protocol = hdlc_type_trans(chan->rx_skb, chan->netdev); + chan->rx_skb->dev = chan->netdev; + skb_reset_mac_header(chan->rx_skb); + chan->netdev->stats.rx_packets++; + chan->netdev->stats.rx_bytes += chan->cosa->rxsize; + netif_rx(chan->rx_skb); + chan->rx_skb = NULL; + return 0; +} + +/* ARGSUSED */ +static int cosa_net_tx_done(struct channel_data *chan, int size) +{ + if (!chan->tx_skb) { + pr_warn("%s: tx_done with empty skb!\n", chan->name); + chan->netdev->stats.tx_errors++; + chan->netdev->stats.tx_aborted_errors++; + return 1; + } + dev_kfree_skb_irq(chan->tx_skb); + chan->tx_skb = NULL; + chan->netdev->stats.tx_packets++; + chan->netdev->stats.tx_bytes += size; + netif_wake_queue(chan->netdev); + return 1; +} + +/*---------- Character device ---------- */ + +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)) { + pr_notice("%s: start the firmware first (status %d)\n", + cosa->name, cosa->firmware_status); + return -EPERM; + } + if (mutex_lock_interruptible(&chan->rlock)) + return -ERESTARTSYS; + + chan->rxdata = kmalloc(COSA_MTU, GFP_DMA|GFP_KERNEL); + if (chan->rxdata == NULL) { + mutex_unlock(&chan->rlock); + 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) { + set_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); + __set_current_state(TASK_RUNNING); + spin_unlock_irqrestore(&cosa->lock, flags); + mutex_unlock(&chan->rlock); + return -ERESTARTSYS; + } + } + remove_wait_queue(&chan->rxwaitq, &wait); + __set_current_state(TASK_RUNNING); + kbuf = chan->rxdata; + count = chan->rxsize; + spin_unlock_irqrestore(&cosa->lock, flags); + mutex_unlock(&chan->rlock); + + 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)) { + pr_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 */ + kbuf = kmalloc(count, GFP_KERNEL|GFP_DMA); + if (kbuf == NULL) { + 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) { + set_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); + __set_current_state(TASK_RUNNING); + chan->tx_status = 1; + spin_unlock_irqrestore(&cosa->lock, flags); + up(&chan->wsem); + kfree(kbuf); + return -ERESTARTSYS; + } + } + remove_wait_queue(&chan->txwaitq, &wait); + __set_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 __poll_t cosa_poll(struct file *file, poll_table *poll) +{ + pr_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; + int ret = 0; + + mutex_lock(&cosa_chardev_mutex); + if ((n=iminor(file_inode(file))>>CARD_MINOR_BITS) + >= nr_cards) { + ret = -ENODEV; + goto out; + } + cosa = cosa_cards+n; + + if ((n=iminor(file_inode(file)) + & ((1<<CARD_MINOR_BITS)-1)) >= cosa->nchannels) { + ret = -ENODEV; + goto out; + } + 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); + ret = -EBUSY; + goto out; + } + 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); +out: + mutex_unlock(&cosa_chardev_mutex); + return ret; +} + +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); + + return fasync_helper(inode, file, on, &fasync[port]); +} +#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) + pr_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) { + pr_notice("cosa%d: reset failed\n", cosa->num); + return -EIO; + } + pr_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) + pr_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)) { + pr_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) { + pr_notice("cosa%d: microcode download failed: %d\n", + cosa->num, i); + return -EIO; + } + pr_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) + pr_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)) { + pr_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) { + pr_notice("cosa%d: reading memory failed: %d\n", cosa->num, i); + return -EIO; + } + pr_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) + pr_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)) { + pr_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) { + pr_notice("cosa%d: start microcode at 0x%04x failed: %d\n", + cosa->num, address, i); + return -EIO; + } + pr_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_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + int rv; + struct channel_data *chan = dev_to_chan(dev); + rv = cosa_ioctl_common(chan->cosa, chan, cmd, + (unsigned long)ifr->ifr_data); + if (rv != -ENOIOCTLCMD) + return rv; + return hdlc_ioctl(dev, ifr, cmd); +} + +static long cosa_chardev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct channel_data *channel = file->private_data; + struct cosa_data *cosa; + long ret; + + mutex_lock(&cosa_chardev_mutex); + cosa = channel->cosa; + ret = cosa_ioctl_common(cosa, channel, cmd, arg); + mutex_unlock(&cosa_chardev_mutex); + return ret; +} + + +/*---------- 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; + + pr_info("cosa%dc%d: starting tx(0x%x)", + chan->cosa->num, chan->num, len); + for (i=0; i<len; i++) + pr_cont(" %02x", buf[i]&0xff); + pr_cont("\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"; + + pr_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) + pr_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) { + pr_info("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); + msleep(500); + /* 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 + pr_info("get_wait_data returning after %d retries\n", + 999-retries); +#endif + return r; + } + /* sleep if not ready to read */ + schedule_timeout_interruptible(1); + } + pr_info("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 + pr_info("Putdata: %d retries\n", 999-retries); +#endif + return 0; + } +#if 0 + /* sleep if not ready to read */ + schedule_timeout_interruptible(1); +#endif + } + pr_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) { + pr_notice("cosa%d: puthexnumber failed to write byte %d\n", + cosa->num, i); + return -1-2*i; + } + if (get_wait_data(cosa) != temp[i]) { + pr_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 + pr_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) { + pr_warn("%s: No channel wants data in TX IRQ. Expect DMA timeout.\n", + 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; + pr_info("busmaster IRQ\n"); + while (!(cosa_getstatus(cosa)&SR_TX_RDY)) { + count++; + udelay(10); + if (count > 1000) break; + } + pr_info("status %x\n", cosa_getstatus(cosa)); + pr_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); + } + pr_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 + pr_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 + pr_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 + pr_info("cosa%d: receive rxsize = (0x%04x)\n", + cosa->num, cosa->rxsize); +#endif + } + if (((cosa->rxsize & 0xe000) >> 13) >= cosa->nchannels) { + pr_warn("%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 */ + pr_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; + pr_info("cosa%dc%d: done rx(0x%x)", + cosa->num, cosa->rxchan->num, cosa->rxsize); + for (i=0; i<cosa->rxsize; i++) + pr_cont(" %02x", cosa->rxbuf[i]&0xff); + pr_cont("\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 { + pr_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_) +{ + unsigned status; + int count = 0; + struct cosa_data *cosa = cosa_; +again: + status = cosa_getstatus(cosa); +#ifdef DEBUG_IRQS + pr_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; + } + pr_info("cosa%d: unknown status 0x%02x in IRQ after %d retries\n", + cosa->num, status & 0xff, count); + } +#ifdef DEBUG_IRQS + if (count) + pr_info("%s: %d-times got unknown status in IRQ\n", + cosa->name, count); + else + pr_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; + } + pr_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) +{ + pr_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) +{ + pr_info("%s: IO: data -> 0x%04x\n", cosa->name, data); +} + +static void debug_data_out(struct cosa_data *cosa, int data) +{ + pr_info("%s: IO: data <- 0x%04x\n", cosa->name, data); +} + +static void debug_data_cmd(struct cosa_data *cosa, int data) +{ + pr_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 000000000..028f3d96b --- /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/dlci.c b/drivers/net/wan/dlci.c new file mode 100644 index 000000000..a0d76f70c --- /dev/null +++ b/drivers/net/wan/dlci.c @@ -0,0 +1,546 @@ +/* + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/io.h> +#include <asm/dma.h> +#include <linux/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, const void *daddr, + const void *saddr, unsigned len) +{ + struct frhdr hdr; + unsigned int hlen; + char *dest; + + 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 frhdr *hdr; + int process, header; + + if (!pskb_may_pull(skb, sizeof(*hdr))) { + netdev_notice(dev, "invalid data no header\n"); + dev->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) + { + netdev_notice(dev, "Invalid header flag 0x%02X\n", + hdr->control); + dev->stats.rx_errors++; + } + else + switch (hdr->IP_NLPID) + { + case FRAD_P_PADDING: + if (hdr->NLPID != FRAD_P_SNAP) + { + netdev_notice(dev, "Unsupported NLPID 0x%02X\n", + hdr->NLPID); + dev->stats.rx_errors++; + break; + } + + if (hdr->OUI[0] + hdr->OUI[1] + hdr->OUI[2] != 0) + { + netdev_notice(dev, "Unsupported organizationally unique identifier 0x%02X-%02X-%02X\n", + hdr->OUI[0], + hdr->OUI[1], + hdr->OUI[2]); + dev->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: + netdev_notice(dev, "Unsupported NLPID 0x%02X\n", + hdr->pad); + dev->stats.rx_errors++; + break; + + default: + netdev_notice(dev, "Invalid pad byte 0x%02X\n", + hdr->pad); + dev->stats.rx_errors++; + break; + } + + if (process) + { + /* we've set up the protocol, so discard the header */ + skb_reset_mac_header(skb); + skb_pull(skb, header); + dev->stats.rx_bytes += skb->len; + netif_rx(skb); + dev->stats.rx_packets++; + } + else + dev_kfree_skb(skb); +} + +static netdev_tx_t dlci_transmit(struct sk_buff *skb, struct net_device *dev) +{ + struct dlci_local *dlp = netdev_priv(dev); + + if (skb) { + struct netdev_queue *txq = skb_get_tx_queue(dev, skb); + netdev_start_xmit(skb, dlp->slave, txq, false); + } + return NETDEV_TX_OK; +} + +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 = netdev_priv(dev); + + flp = netdev_priv(dlp->slave); + + 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 = netdev_priv(dev); + + 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); + + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int dlci_change_mtu(struct net_device *dev, int new_mtu) +{ + struct dlci_local *dlp = netdev_priv(dev); + + return dev_set_mtu(dlp->slave, new_mtu); +} + +static int dlci_open(struct net_device *dev) +{ + struct dlci_local *dlp; + struct frad_local *flp; + int err; + + dlp = netdev_priv(dev); + + if (!*(short *)(dev->dev_addr)) + return -EINVAL; + + if (!netif_running(dlp->slave)) + return -ENOTCONN; + + flp = netdev_priv(dlp->slave); + 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 = netdev_priv(dev); + + flp = netdev_priv(dlp->slave); + err = (*flp->deactivate)(dlp->slave, dev); + + return 0; +} + +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(&init_net, dlci->devname); + if (!slave) + return -ENODEV; + + if (slave->type != ARPHRD_FRAD || netdev_priv(slave) == NULL) + goto err1; + + /* create device name */ + master = alloc_netdev(sizeof(struct dlci_local), "dlci%d", + NET_NAME_UNKNOWN, 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; + } + } + + *(short *)(master->dev_addr) = dlci->dlci; + + dlp = netdev_priv(master); + dlp->slave = slave; + dlp->master = master; + + flp = netdev_priv(slave); + 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; + bool found = false; + + rtnl_lock(); + + /* validate slave device */ + master = __dev_get_by_name(&init_net, dlci->devname); + if (!master) { + err = -ENODEV; + goto out; + } + + list_for_each_entry(dlp, &dlci_devs, list) { + if (dlp->master == master) { + found = true; + break; + } + } + if (!found) { + err = -ENODEV; + goto out; + } + + if (netif_running(master)) { + err = -EBUSY; + goto out; + } + + dlp = netdev_priv(master); + slave = dlp->slave; + flp = netdev_priv(slave); + + err = (*flp->deassoc)(slave, master); + if (!err) { + list_del(&dlp->list); + + unregister_netdevice(master); + + dev_put(slave); + } +out: + 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 const struct header_ops dlci_header_ops = { + .create = dlci_header, +}; + +static const struct net_device_ops dlci_netdev_ops = { + .ndo_open = dlci_open, + .ndo_stop = dlci_close, + .ndo_do_ioctl = dlci_dev_ioctl, + .ndo_start_xmit = dlci_transmit, + .ndo_change_mtu = dlci_change_mtu, +}; + +static void dlci_setup(struct net_device *dev) +{ + struct dlci_local *dlp = netdev_priv(dev); + + dev->flags = 0; + dev->header_ops = &dlci_header_ops; + dev->netdev_ops = &dlci_netdev_ops; + dev->needs_free_netdev = true; + + 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 = netdev_notifier_info_to_dev(ptr); + + if (dev_net(dev) != &init_net) + return NOTIFY_DONE; + + 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 000000000..c0b0f525c --- /dev/null +++ b/drivers/net/wan/dscc4.c @@ -0,0 +1,2057 @@ +/* + * 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 mystery. 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/sched.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 <linux/slab.h> + +#include <asm/cache.h> +#include <asm/byteorder.h> +#include <linux/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/string.h> + +#include <linux/if_arp.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/delay.h> +#include <linux/hdlc.h> +#include <linux/mutex.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 DEFINE_MUTEX(dscc4_mutex); +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 Controller"); +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 { + __le32 state; + __le32 next; + __le32 data; + __le32 complete; + u32 jiffies; /* Allows sizeof(TxFD) == sizeof(RxFD) + extra hack */ + /* FWIW, datasheet calls that "dummy" and says that card + * never looks at it; neither does the driver */ +}; + +struct RxFD { + __le32 state1; + __le32 next; + __le32 data; + __le32 state2; + __le32 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 { + __le32 *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; + __le32 *iqrx; + __le32 *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 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 cpu_to_le32(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 cpu_to_le32(0x00000200) +#define FrameEnd cpu_to_le32(0x80000000) +#define DataComplete cpu_to_le32(0x40000000) +#define LengthCheck 0x00008000 +#define SccEvt 0x02000000 +#define NoAck 0x00000200 +#define Action 0x00000001 +#define HiDesc cpu_to_le32(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 netdev_tx_t 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_tx_timeout(struct net_device *); +static irqreturn_t dscc4_irq(int irq, void *dev_id); +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; +} + +static 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; +} + +static 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 device *d = &dpriv->pci_priv->pdev->dev; + struct TxFD *tx_fd = dpriv->tx_fd; + struct RxFD *rx_fd = dpriv->rx_fd; + struct sk_buff **skbuff; + int i; + + dma_free_coherent(d, TX_TOTAL_SIZE, tx_fd, dpriv->tx_fd_dma); + dma_free_coherent(d, RX_TOTAL_SIZE, rx_fd, dpriv->rx_fd_dma); + + skbuff = dpriv->tx_skbuff; + for (i = 0; i < TX_RING_SIZE; i++) { + if (*skbuff) { + dma_unmap_single(d, le32_to_cpu(tx_fd->data), + (*skbuff)->len, DMA_TO_DEVICE); + dev_kfree_skb(*skbuff); + } + skbuff++; + tx_fd++; + } + + skbuff = dpriv->rx_skbuff; + for (i = 0; i < RX_RING_SIZE; i++) { + if (*skbuff) { + dma_unmap_single(d, le32_to_cpu(rx_fd->data), + RX_MAX(HDLC_MAX_MRU), + DMA_FROM_DEVICE); + dev_kfree_skb(*skbuff); + } + skbuff++; + rx_fd++; + } +} + +static 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 device *d = &dpriv->pci_priv->pdev->dev; + struct RxFD *rx_fd = dpriv->rx_fd + dirty; + const int len = RX_MAX(HDLC_MAX_MRU); + struct sk_buff *skb; + dma_addr_t addr; + + skb = dev_alloc_skb(len); + if (!skb) + goto err_out; + + skb->protocol = hdlc_type_trans(skb, dev); + addr = dma_map_single(d, skb->data, len, DMA_FROM_DEVICE); + if (dma_mapping_error(d, addr)) + goto err_free_skb; + + dpriv->rx_skbuff[dirty] = skb; + rx_fd->data = cpu_to_le32(addr); + return 0; + +err_free_skb: + dev_kfree_skb_any(skb); +err_out: + rx_fd->data = 0; + return -1; +} + +/* + * 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; + } + schedule_timeout_uninterruptible(msecs_to_jiffies(100)); + rmb(); + } while (++i > 0); + netdev_err(dev, "%s timeout\n", 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) { + netdev_dbg(dev, "%s ack\n", msg); + writel(ArAck, ioaddr); + goto done; + } else if (state & Arf) { + netdev_err(dev, "%s failed\n", msg); + writel(Arf, ioaddr); + i = -1; + goto done; + } + rmb(); + } while (++i > 0); + netdev_err(dev, "%s timeout\n", 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] & cpu_to_le32(Xpr))) + break; + smp_rmb(); + schedule_timeout_uninterruptible(msecs_to_jiffies(100)); + } 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) + netdev_err(dev, "Tx reset failed\n"); +} +#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 device *d = &dpriv->pci_priv->pdev->dev; + 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, __func__); + goto refill; + } + pkt_len = TO_SIZE(le32_to_cpu(rx_fd->state2)); + dma_unmap_single(d, le32_to_cpu(rx_fd->data), + RX_MAX(HDLC_MAX_MRU), DMA_FROM_DEVICE); + if ((skb->data[--pkt_len] & FrameOk) == FrameOk) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += pkt_len; + skb_put(skb, pkt_len); + if (netif_running(dev)) + skb->protocol = hdlc_type_trans(skb, dev); + netif_rx(skb); + } else { + if (skb->data[pkt_len] & FrameRdo) + dev->stats.rx_fifo_errors++; + else if (!(skb->data[pkt_len] & FrameCrc)) + dev->stats.rx_crc_errors++; + else if ((skb->data[pkt_len] & (FrameVfr | FrameRab)) != + (FrameVfr | FrameRab)) + dev->stats.rx_length_errors++; + dev->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 = cpu_to_le32(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)); + + for (i = 0; i < dev_per_card; i++) + free_netdev(root[i].dev); + kfree(root); + kfree(ppriv); +} + +static int 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) { + pr_err("can't reserve MMIO region (regs)\n"); + goto err_disable_0; + } + rc = pci_request_region(pdev, 1, "LBI interface"); + if (rc < 0) { + pr_err("can't reserve MMIO region (lbi)\n"); + goto err_free_mmio_region_1; + } + + ioaddr = pci_ioremap_bar(pdev, 0); + if (!ioaddr) { + pr_err("cannot remap MMIO region %llx @ %llx\n", + (unsigned long long)pci_resource_len(pdev, 0), + (unsigned long long)pci_resource_start(pdev, 0)); + rc = -EIO; + goto err_free_mmio_regions_2; + } + printk(KERN_DEBUG "Siemens DSCC4, MMIO at %#llx (regs), %#llx (lbi), IRQ %d\n", + (unsigned long long)pci_resource_start(pdev, 0), + (unsigned long long)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, IRQF_SHARED, DRV_NAME, priv->root); + if (rc < 0) { + pr_warn("IRQ %d busy\n", 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); + + rc = -ENOMEM; + + priv->iqcfg = (__le32 *)dma_alloc_coherent(&pdev->dev, + IRQ_RING_SIZE*sizeof(__le32), &priv->iqcfg_dma, GFP_KERNEL); + if (!priv->iqcfg) + goto err_free_irq_5; + writel(priv->iqcfg_dma, ioaddr + IQCFG); + + /* + * 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 = (__le32 *)dma_alloc_coherent(&pdev->dev, + IRQ_RING_SIZE*sizeof(u32), &dpriv->iqtx_dma, + GFP_KERNEL); + 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 = (__le32 *)dma_alloc_coherent(&pdev->dev, + IRQ_RING_SIZE*sizeof(u32), &dpriv->iqrx_dma, + GFP_KERNEL); + 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; + dma_free_coherent(&pdev->dev, 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; + dma_free_coherent(&pdev->dev, IRQ_RING_SIZE*sizeof(u32), + dpriv->iqtx, dpriv->iqtx_dma); + } + dma_free_coherent(&pdev->dev, 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 const struct net_device_ops dscc4_ops = { + .ndo_open = dscc4_open, + .ndo_stop = dscc4_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = dscc4_ioctl, + .ndo_tx_timeout = dscc4_tx_timeout, +}; + +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 = kcalloc(dev_per_card, sizeof(*root), GFP_KERNEL); + if (!root) + goto err_out; + + for (i = 0; i < dev_per_card; i++) { + root[i].dev = alloc_hdlcdev(root + i); + if (!root[i].dev) + goto err_free_dev; + } + + ppriv = kzalloc(sizeof(*ppriv), GFP_KERNEL); + if (!ppriv) + goto err_free_dev; + + 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->irq = pdev->irq; + d->netdev_ops = &dscc4_ops; + d->watchdog_timeo = TX_TIMEOUT; + 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) { + pr_err("unable to register\n"); + 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; +}; + +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); + + netdev_info(dev, "loopback requires clock\n"); + 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; + + mutex_lock(&dscc4_mutex); + 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); + + schedule_timeout_uninterruptible(msecs_to_jiffies(100)); + + for (i = 0; i < 16; i++) + pci_write_config_dword(pdev, i << 2, dscc4_pci_config_store[i]); + mutex_unlock(&dscc4_mutex); +} +#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); + int ret = -EAGAIN; + + if ((dscc4_loopback_check(dpriv) < 0)) + goto err; + + if ((ret = hdlc_open(dev))) + goto err; + + /* + * Due to various bugs, there is no way to reliably reset a + * specific port (manufacturer's dependent 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); + netdev_info(dev, "up again\n"); + 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) { + netdev_err(dev, "busy - try later\n"); + ret = -EAGAIN; + goto err_out; + } else + netdev_info(dev, "available - good\n"); + + 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) { + pr_err("XPR timeout\n"); + goto err_disable_scc_events; + } + + if (debug > 2) + dscc4_tx_print(dev, dpriv, "Open"); + +done: + netif_start_queue(dev); + + 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 netdev_tx_t dscc4_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct dscc4_dev_priv *dpriv = dscc4_priv(dev); + struct device *d = &dpriv->pci_priv->pdev->dev; + struct TxFD *tx_fd; + dma_addr_t addr; + int next; + + addr = dma_map_single(d, skb->data, skb->len, DMA_TO_DEVICE); + if (dma_mapping_error(d, addr)) { + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + 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 = cpu_to_le32(addr); + 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 + + 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 NETDEV_TX_OK; +} + +static int dscc4_close(struct net_device *dev) +{ + struct dscc4_dev_priv *dpriv = dscc4_priv(dev); + + 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 laziness. + * + * The clock signals wiring can be (is ?) manufacturer dependent. 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) { + netdev_info(dev, "please reset the device before this command\n"); + 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(const 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) +{ + static const 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) +{ + static const 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 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) { + netdev_err(dev, "failure (Arf). Harass the maintainer\n"); + 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] & cpu_to_le32(Arf)) + netdev_err(dev, "CFG failed\n"); + 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 = le32_to_cpu(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 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) { + dma_unmap_single(&ppriv->pdev->dev, + le32_to_cpu(tx_fd->data), + skb->len, DMA_TO_DEVICE); + if (tx_fd->state & FrameEnd) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + } + dev_kfree_skb_irq(skb); + dpriv->tx_skbuff[cur] = NULL; + ++dpriv->tx_dirty; + } else { + if (debug > 1) + netdev_err(dev, "Tx: NULL skb %d\n", + 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) { + netdev_err(dev, "Tx Data Underrun. Ask maintainer\n"); + 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) { + netdev_info(dev, "CTS transition\n"); + if (!(state &= ~Cts)) /* DEBUG */ + goto try; + } + if (state & Xmr) { + /* Frame needs to be sent again - FIXME */ + netdev_err(dev, "Tx ReTx. Ask maintainer\n"); + if (!(state &= ~Xmr)) /* DEBUG */ + goto try; + } + if (state & Xpr) { + void __iomem *scc_addr; + unsigned long ring; + unsigned 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) + netdev_info(dev, "busy in irq\n"); + + 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) + netdev_info(dev, "CD transition\n"); + if (!(state &= ~Cd)) /* DEBUG */ + goto try; + } + } else { /* ! SccEvt */ + if (state & Hi) { +#ifdef DSCC4_POLLING + while (!dscc4_tx_poll(dpriv, dev)); +#endif + netdev_info(dev, "Tx Hi\n"); + state &= ~Hi; + } + if (state & Err) { + netdev_info(dev, "Tx ERR\n"); + dev->stats.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 = le32_to_cpu(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 = cpu_to_le32(0xbabeface); + //} + goto try; + } + if (state & Fi) { + dscc4_rx_skb(dpriv, dev); + goto try; + } + if (state & Hi ) { /* HI bit */ + netdev_info(dev, "Rx Hi\n"); + 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) { + netdev_info(dev, "CTS transition\n"); + 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) { + dev->stats.rx_over_errors++; + rx_fd->state1 |= Hold; + rx_fd->state2 = 0x00000000; + rx_fd->end = cpu_to_le32(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) { + netdev_err(dev, "RDO recovery failed(RDR)\n"); + goto rdo_end; + } + writel(MTFi|Idr, scc_addr + CH0CFG); + if (dscc4_do_action(dev, "IDR") < 0) { + netdev_err(dev, "RDO recovery failed(IDR)\n"); + goto rdo_end; + } + rdo_end: +#endif + scc_patchl(0, RxActivate, dpriv, dev, CCR2); + goto try; + } + if (state & Cd) { + netdev_info(dev, "CD transition\n"); + 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/ + */ +static 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) { + struct device *d = &dpriv->pci_priv->pdev->dev; + int last = dpriv->tx_dirty%TX_RING_SIZE; + struct TxFD *tx_fd = dpriv->tx_fd + last; + dma_addr_t addr; + + skb->len = DUMMY_SKB_SIZE; + skb_copy_to_linear_data(skb, version, + strlen(version) % DUMMY_SKB_SIZE); + addr = dma_map_single(d, skb->data, DUMMY_SKB_SIZE, + DMA_TO_DEVICE); + if (dma_mapping_error(d, addr)) { + dev_kfree_skb_any(skb); + return NULL; + } + tx_fd->state = FrameEnd | TO_STATE_TX(DUMMY_SKB_SIZE); + tx_fd->data = cpu_to_le32(addr); + 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 device *d = &dpriv->pci_priv->pdev->dev; + struct TxFD *tx_fd; + struct RxFD *rx_fd; + void *ring; + int i; + + ring = dma_alloc_coherent(d, RX_TOTAL_SIZE, &dpriv->rx_fd_dma, + GFP_KERNEL); + if (!ring) + goto err_out; + dpriv->rx_fd = rx_fd = (struct RxFD *) ring; + + ring = dma_alloc_coherent(d, TX_TOTAL_SIZE, &dpriv->tx_fd_dma, + GFP_KERNEL); + 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 = cpu_to_le32(dpriv->tx_fd_dma); + (tx_fd++)->next = cpu_to_le32(dpriv->tx_fd_dma + + (++i%TX_RING_SIZE)*sizeof(*tx_fd)); + } while (i < TX_RING_SIZE); + + if (!dscc4_init_dummy_skb(dpriv)) + 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 = cpu_to_le32(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 = cpu_to_le32(dpriv->rx_fd_dma + + (++i%RX_RING_SIZE)*sizeof(*rx_fd)); + } while (i < RX_RING_SIZE); + + return 0; + +err_free_dma_tx: + dma_free_coherent(d, TX_TOTAL_SIZE, ring, dpriv->tx_fd_dma); +err_free_dma_rx: + dma_free_coherent(d, RX_TOTAL_SIZE, rx_fd, dpriv->rx_fd_dma); +err_out: + return -ENOMEM; +} + +static void 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); + dma_free_coherent(&pdev->dev, 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); + dma_free_coherent(&pdev->dev, IRQ_RING_SIZE*sizeof(u32), + dpriv->iqrx, dpriv->iqrx_dma); + dma_free_coherent(&pdev->dev, 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 const 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 = dscc4_remove_one, +}; + +module_pci_driver(dscc4_driver); diff --git a/drivers/net/wan/farsync.c b/drivers/net/wan/farsync.c new file mode 100644 index 000000000..2a3f0f1a2 --- /dev/null +++ b/drivers/net/wan/farsync.c @@ -0,0 +1,2670 @@ +/* + * 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> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/if.h> +#include <linux/hdlc.h> +#include <asm/io.h> +#include <linux/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 */ +#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 variables + */ +static int fst_txq_low = FST_LOW_WATER_MARK; +static int fst_txq_high = FST_HIGH_WATER_MARK; +static int fst_max_reads = 7; +static int fst_excluded_cards = 0; +static 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, args...) \ +do { \ + if (fst_debug_mask & (F)) \ + printk(KERN_DEBUG pr_fmt(fmt), ##args); \ +} while (0) +#else +#define dbg(F, fmt, args...) \ +do { \ + if (0) \ + printk(KERN_DEBUG pr_fmt(fmt), ##args); \ +} while (0) +#endif + +/* + * PCI ID lookup table + */ +static const struct pci_device_id fst_pci_dev_id[] = { + {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); + +static DECLARE_TASKLET(fst_tx_task, fst_process_tx_work_q, 0); +static DECLARE_TASKLET(fst_int_task, fst_process_int_work_q, 0); + +static struct fst_card_info *fst_card_array[FST_MAX_CARDS]; +static spinlock_t fst_work_q_lock; +static u64 fst_work_txq; +static 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 = (u64)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 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 + */ + usleep_range(10, 20); + outw(0x240f, card->pci_conf + CNTRL_9054 + 2); + /* + * We are delaying here to allow the 9054 to reload its eeprom + */ + usleep_range(10, 20); + 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_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); + + /* + * 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); + dev->stats.tx_packets++; + dev->stats.tx_bytes += len; + netif_trans_update(dev); +} + +/* + * Mark it for our own raw sockets interface + */ +static __be16 farsync_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + skb->dev = dev; + skb_reset_mac_header(skb); + 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); + int pi; + int rx_status; + + dbg(DBG_TX, "fst_rx_dma_complete\n"); + pi = port->index; + skb_put_data(skb, card->rx_dma_handle_host, len); + + /* Reset buffer descriptor */ + FST_WRB(card, rxDescrRing[pi][rxp].bits, DMA_OWN); + + /* Update stats */ + dev->stats.rx_packets++; + dev->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) + dev->stats.rx_dropped++; +} + +/* + * Receive a frame through the DMA + */ +static inline void +fst_rx_dma(struct fst_card_info *card, dma_addr_t dma, u32 mem, int len) +{ + /* + * This routine will setup the DMA and start it + */ + + dbg(DBG_RX, "In fst_rx_dma %x %x %d\n", (u32)dma, mem, len); + if (card->dmarx_in_progress) { + dbg(DBG_ASS, "In fst_rx_dma while dma in progress\n"); + } + + outl(dma, card->pci_conf + DMAPADR0); /* Copy to here */ + outl(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, dma_addr_t dma, u32 mem, int len) +{ + /* + * This routine will setup the DMA and start it. + */ + + dbg(DBG_TX, "In fst_tx_dma %x %x %d\n", (u32)dma, mem, len); + if (card->dmatx_in_progress) { + dbg(DBG_ASS, "In fst_tx_dma while dma in progress\n"); + } + + outl(dma, card->pci_conf + DMAPADR1); /* Copy from here */ + outl(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_uninterruptible(1); + spin_lock_irqsave(&card->card_lock, flags); + + if (++safety > 2000) { + pr_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); + + /* + * Increment the appropriate error counter + */ + dev->stats.rx_errors++; + if (dmabits & RX_OFLO) { + dev->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) { + dev->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) { + dev->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)) { + dev->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); + + /* 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. + */ + pr_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"); + + dev->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 */ + dev->stats.rx_packets++; + dev->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) + dev->stats.rx_dropped++; + } else { + card->dma_skb_rx = skb; + card->dma_port_rx = port; + card->dma_len_rx = len; + card->dma_rxpos = rxp; + fst_rx_dma(card, card->rx_dma_handle_card, + 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; + + /* + * 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); + 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); + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + netif_trans_update(dev); + } 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, + card->tx_dma_handle_card, + 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 + */ +static irqreturn_t +fst_intr(int dummy, void *dev_id) +{ + struct fst_card_info *card = dev_id; + 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; + + /* + * 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", card->irq, card); + if (card->state != FST_RUNNING) { + pr_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); + port_to_dev(port)->stats.tx_errors++; + port_to_dev(port)->stats.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: + pr_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) { + pr_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) { + pr_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) { + pr_err("Firmware initialisation failed. Card halted\n"); + card->state = FST_HALTED; + return; + } else if (i != 0x00) { + pr_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) { + pr_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; + memset(&sync, 0, sizeof(sync)); + 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; + void *buf; + + 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. */ + + buf = memdup_user(ifr->ifr_data + sizeof(struct fstioc_write), + wrthdr.size); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + memcpy_toio(card->mem + wrthdr.offset, buf, wrthdr.size); + kfree(buf); + + /* 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) { + pr_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; + + /* 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)); + + 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) { + module_put(THIS_MODULE); + 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; + + port = dev_to_port(dev); + card = port->card; + dev->stats.tx_errors++; + dev->stats.tx_aborted_errors++; + dbg(DBG_ASS, "Tx timeout card %d port %d\n", + card->card_no, port->index); + fst_issue_cmd(port, ABORTTX); + + netif_trans_update(dev); + netif_wake_queue(dev); + port->start = 0; +} + +static netdev_tx_t +fst_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct fst_card_info *card; + struct fst_port_info *port; + 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); + dev->stats.tx_errors++; + dev->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 NETDEV_TX_OK; + } + + /* 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); + dev->stats.tx_errors++; + return NETDEV_TX_OK; + } + + /* + * 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); + dev->stats.tx_errors++; + dbg(DBG_ASS, "Tx queue overflow card %d port %d\n", + card->card_no, port->index); + return NETDEV_TX_OK; + } + + /* + * 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 NETDEV_TX_OK; +} + +/* + * 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[] = { + "no hardware", /* Should never be seen */ + "FarSync T2P", + "FarSync T4P", + "FarSync T1U", + "FarSync T2U", + "FarSync T4U", + "FarSync TE1" +}; + +static int +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) { + pr_err("Cannot register HDLC device for port %d (errno %d)\n", + i, -err); + while (i--) + unregister_hdlc_device(card->ports[i].dev); + return err; + } + } + + pr_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); + return 0; +} + +static const struct net_device_ops fst_ops = { + .ndo_open = fst_open, + .ndo_stop = fst_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = fst_ioctl, + .ndo_tx_timeout = fst_tx_timeout, +}; + +/* + * Initialise card when detected. + * Returns 0 to indicate success, or errno otherwise. + */ +static int +fst_add_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + static int no_of_cards_added = 0; + struct fst_card_info *card; + int err = 0; + int i; + + printk_once(KERN_INFO + pr_fmt("FarSync WAN driver " FST_USER_VERSION + " (c) 2001-2004 FarSite Communications Ltd.\n")); +#if FST_DEBUG + dbg(DBG_ASS, "The value of debug mask is %x\n", fst_debug_mask); +#endif + /* + * 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]) { + pr_info("FarSync PCI device %d not assigned\n", + (pdev->devfn) >> 3); + return -EBUSY; + } + } + } + + /* Allocate driver private data */ + card = kzalloc(sizeof(struct fst_card_info), GFP_KERNEL); + if (card == NULL) + return -ENOMEM; + + /* Try to enable the device */ + if ((err = pci_enable_device(pdev)) != 0) { + pr_err("Failed to enable card. Err %d\n", -err); + goto enable_fail; + } + + if ((err = pci_request_regions(pdev, "FarSync")) !=0) { + pr_err("Failed to allocate regions. Err %d\n", -err); + goto regions_fail; + } + + /* 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) { + pr_err("Physical memory remap failed\n"); + err = -ENODEV; + goto ioremap_physmem_fail; + } + if ((card->ctlmem = ioremap(card->phys_ctlmem, 0x10)) == NULL) { + pr_err("Control memory remap failed\n"); + err = -ENODEV; + goto ioremap_ctlmem_fail; + } + dbg(DBG_PCI, "kernel mem %p, ctlmem %p\n", card->mem, card->ctlmem); + + /* Register the interrupt handler */ + if (request_irq(pdev->irq, fst_intr, IRQF_SHARED, FST_DEV_NAME, card)) { + pr_err("Unable to register interrupt %d\n", card->irq); + err = -ENODEV; + goto irq_fail; + } + + /* 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); + pr_err("FarSync: out of memory\n"); + err = -ENOMEM; + goto hdlcdev_fail; + } + 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 - 1][LEN_RX_BUFFER - 1]); + dev->base_addr = card->pci_conf; + dev->irq = card->irq; + + dev->netdev_ops = &fst_ops; + dev->tx_queue_len = FST_TX_QUEUE_LEN; + dev->watchdog_timeo = 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 */ + if (no_of_cards_added >= FST_MAX_CARDS) { + pr_err("FarSync: too many cards\n"); + err = -ENOMEM; + goto card_array_fail; + } + fst_card_array[no_of_cards_added] = card; + card->card_no = no_of_cards_added++; /* Record instance and bump it */ + err = fst_init_card(card); + if (err) + goto init_card_fail; + 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) { + pr_err("Could not allocate rx dma buffer\n"); + err = -ENOMEM; + goto rx_dma_fail; + } + 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) { + pr_err("Could not allocate tx dma buffer\n"); + err = -ENOMEM; + goto tx_dma_fail; + } + } + return 0; /* Success */ + +tx_dma_fail: + pci_free_consistent(card->device, FST_MAX_MTU, + card->rx_dma_handle_host, + card->rx_dma_handle_card); +rx_dma_fail: + fst_disable_intr(card); + for (i = 0 ; i < card->nports ; i++) + unregister_hdlc_device(card->ports[i].dev); +init_card_fail: + fst_card_array[card->card_no] = NULL; +card_array_fail: + for (i = 0 ; i < card->nports ; i++) + free_netdev(card->ports[i].dev); +hdlcdev_fail: + free_irq(card->irq, card); +irq_fail: + iounmap(card->ctlmem); +ioremap_ctlmem_fail: + iounmap(card->mem); +ioremap_physmem_fail: + pci_release_regions(pdev); +regions_fail: + pci_disable_device(pdev); +enable_fail: + kfree(card); + return err; +} + +/* + * Cleanup and close down a card + */ +static void +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 = 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_register_driver(&fst_driver); +} + +static void __exit +fst_cleanup_module(void) +{ + pr_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 000000000..6b27e7c3d --- /dev/null +++ b/drivers/net/wan/farsync.h @@ -0,0 +1,351 @@ +/* + * 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 + */ +#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_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/fsl_ucc_hdlc.c b/drivers/net/wan/fsl_ucc_hdlc.c new file mode 100644 index 000000000..5df6e85e7 --- /dev/null +++ b/drivers/net/wan/fsl_ucc_hdlc.c @@ -0,0 +1,1183 @@ +/* Freescale QUICC Engine HDLC Device Driver + * + * Copyright 2016 Freescale Semiconductor 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. + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/hdlc.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/stddef.h> +#include <soc/fsl/qe/qe_tdm.h> +#include <uapi/linux/if_arp.h> + +#include "fsl_ucc_hdlc.h" + +#define DRV_DESC "Freescale QE UCC HDLC Driver" +#define DRV_NAME "ucc_hdlc" + +#define TDM_PPPOHT_SLIC_MAXIN + +static struct ucc_tdm_info utdm_primary_info = { + .uf_info = { + .tsa = 0, + .cdp = 0, + .cds = 1, + .ctsp = 1, + .ctss = 1, + .revd = 0, + .urfs = 256, + .utfs = 256, + .urfet = 128, + .urfset = 192, + .utfet = 128, + .utftt = 0x40, + .ufpt = 256, + .mode = UCC_FAST_PROTOCOL_MODE_HDLC, + .ttx_trx = UCC_FAST_GUMR_TRANSPARENT_TTX_TRX_NORMAL, + .tenc = UCC_FAST_TX_ENCODING_NRZ, + .renc = UCC_FAST_RX_ENCODING_NRZ, + .tcrc = UCC_FAST_16_BIT_CRC, + .synl = UCC_FAST_SYNC_LEN_NOT_USED, + }, + + .si_info = { +#ifdef TDM_PPPOHT_SLIC_MAXIN + .simr_rfsd = 1, + .simr_tfsd = 2, +#else + .simr_rfsd = 0, + .simr_tfsd = 0, +#endif + .simr_crt = 0, + .simr_sl = 0, + .simr_ce = 1, + .simr_fe = 1, + .simr_gm = 0, + }, +}; + +static struct ucc_tdm_info utdm_info[UCC_MAX_NUM]; + +static int uhdlc_init(struct ucc_hdlc_private *priv) +{ + struct ucc_tdm_info *ut_info; + struct ucc_fast_info *uf_info; + u32 cecr_subblock; + u16 bd_status; + int ret, i; + void *bd_buffer; + dma_addr_t bd_dma_addr; + u32 riptr; + u32 tiptr; + u32 gumr; + + ut_info = priv->ut_info; + uf_info = &ut_info->uf_info; + + if (priv->tsa) { + uf_info->tsa = 1; + uf_info->ctsp = 1; + } + + /* This sets HPM register in CMXUCR register which configures a + * open drain connected HDLC bus + */ + if (priv->hdlc_bus) + uf_info->brkpt_support = 1; + + uf_info->uccm_mask = ((UCC_HDLC_UCCE_RXB | UCC_HDLC_UCCE_RXF | + UCC_HDLC_UCCE_TXB) << 16); + + ret = ucc_fast_init(uf_info, &priv->uccf); + if (ret) { + dev_err(priv->dev, "Failed to init uccf."); + return ret; + } + + priv->uf_regs = priv->uccf->uf_regs; + ucc_fast_disable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX); + + /* Loopback mode */ + if (priv->loopback) { + dev_info(priv->dev, "Loopback Mode\n"); + /* use the same clock when work in loopback */ + qe_setbrg(ut_info->uf_info.rx_clock, 20000000, 1); + + gumr = ioread32be(&priv->uf_regs->gumr); + gumr |= (UCC_FAST_GUMR_LOOPBACK | UCC_FAST_GUMR_CDS | + UCC_FAST_GUMR_TCI); + gumr &= ~(UCC_FAST_GUMR_CTSP | UCC_FAST_GUMR_RSYN); + iowrite32be(gumr, &priv->uf_regs->gumr); + } + + /* Initialize SI */ + if (priv->tsa) + ucc_tdm_init(priv->utdm, priv->ut_info); + + /* Write to QE CECR, UCCx channel to Stop Transmission */ + cecr_subblock = ucc_fast_get_qe_cr_subblock(uf_info->ucc_num); + ret = qe_issue_cmd(QE_STOP_TX, cecr_subblock, + QE_CR_PROTOCOL_UNSPECIFIED, 0); + + /* Set UPSMR normal mode (need fixed)*/ + iowrite32be(0, &priv->uf_regs->upsmr); + + /* hdlc_bus mode */ + if (priv->hdlc_bus) { + u32 upsmr; + + dev_info(priv->dev, "HDLC bus Mode\n"); + upsmr = ioread32be(&priv->uf_regs->upsmr); + + /* bus mode and retransmit enable, with collision window + * set to 8 bytes + */ + upsmr |= UCC_HDLC_UPSMR_RTE | UCC_HDLC_UPSMR_BUS | + UCC_HDLC_UPSMR_CW8; + iowrite32be(upsmr, &priv->uf_regs->upsmr); + + /* explicitly disable CDS & CTSP */ + gumr = ioread32be(&priv->uf_regs->gumr); + gumr &= ~(UCC_FAST_GUMR_CDS | UCC_FAST_GUMR_CTSP); + /* set automatic sync to explicitly ignore CD signal */ + gumr |= UCC_FAST_GUMR_SYNL_AUTO; + iowrite32be(gumr, &priv->uf_regs->gumr); + } + + priv->rx_ring_size = RX_BD_RING_LEN; + priv->tx_ring_size = TX_BD_RING_LEN; + /* Alloc Rx BD */ + priv->rx_bd_base = dma_alloc_coherent(priv->dev, + RX_BD_RING_LEN * sizeof(struct qe_bd), + &priv->dma_rx_bd, GFP_KERNEL); + + if (!priv->rx_bd_base) { + dev_err(priv->dev, "Cannot allocate MURAM memory for RxBDs\n"); + ret = -ENOMEM; + goto free_uccf; + } + + /* Alloc Tx BD */ + priv->tx_bd_base = dma_alloc_coherent(priv->dev, + TX_BD_RING_LEN * sizeof(struct qe_bd), + &priv->dma_tx_bd, GFP_KERNEL); + + if (!priv->tx_bd_base) { + dev_err(priv->dev, "Cannot allocate MURAM memory for TxBDs\n"); + ret = -ENOMEM; + goto free_rx_bd; + } + + /* Alloc parameter ram for ucc hdlc */ + priv->ucc_pram_offset = qe_muram_alloc(sizeof(struct ucc_hdlc_param), + ALIGNMENT_OF_UCC_HDLC_PRAM); + + if (IS_ERR_VALUE(priv->ucc_pram_offset)) { + dev_err(priv->dev, "Can not allocate MURAM for hdlc parameter.\n"); + ret = -ENOMEM; + goto free_tx_bd; + } + + priv->rx_skbuff = kcalloc(priv->rx_ring_size, + sizeof(*priv->rx_skbuff), + GFP_KERNEL); + if (!priv->rx_skbuff) { + ret = -ENOMEM; + goto free_ucc_pram; + } + + priv->tx_skbuff = kcalloc(priv->tx_ring_size, + sizeof(*priv->tx_skbuff), + GFP_KERNEL); + if (!priv->tx_skbuff) { + ret = -ENOMEM; + goto free_rx_skbuff; + } + + priv->skb_curtx = 0; + priv->skb_dirtytx = 0; + priv->curtx_bd = priv->tx_bd_base; + priv->dirty_tx = priv->tx_bd_base; + priv->currx_bd = priv->rx_bd_base; + priv->currx_bdnum = 0; + + /* init parameter base */ + cecr_subblock = ucc_fast_get_qe_cr_subblock(uf_info->ucc_num); + ret = qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, cecr_subblock, + QE_CR_PROTOCOL_UNSPECIFIED, priv->ucc_pram_offset); + + priv->ucc_pram = (struct ucc_hdlc_param __iomem *) + qe_muram_addr(priv->ucc_pram_offset); + + /* Zero out parameter ram */ + memset_io(priv->ucc_pram, 0, sizeof(struct ucc_hdlc_param)); + + /* Alloc riptr, tiptr */ + riptr = qe_muram_alloc(32, 32); + if (IS_ERR_VALUE(riptr)) { + dev_err(priv->dev, "Cannot allocate MURAM mem for Receive internal temp data pointer\n"); + ret = -ENOMEM; + goto free_tx_skbuff; + } + + tiptr = qe_muram_alloc(32, 32); + if (IS_ERR_VALUE(tiptr)) { + dev_err(priv->dev, "Cannot allocate MURAM mem for Transmit internal temp data pointer\n"); + ret = -ENOMEM; + goto free_riptr; + } + if (riptr != (u16)riptr || tiptr != (u16)tiptr) { + dev_err(priv->dev, "MURAM allocation out of addressable range\n"); + ret = -ENOMEM; + goto free_tiptr; + } + + /* Set RIPTR, TIPTR */ + iowrite16be(riptr, &priv->ucc_pram->riptr); + iowrite16be(tiptr, &priv->ucc_pram->tiptr); + + /* Set MRBLR */ + iowrite16be(MAX_RX_BUF_LENGTH, &priv->ucc_pram->mrblr); + + /* Set RBASE, TBASE */ + iowrite32be(priv->dma_rx_bd, &priv->ucc_pram->rbase); + iowrite32be(priv->dma_tx_bd, &priv->ucc_pram->tbase); + + /* Set RSTATE, TSTATE */ + iowrite32be(BMR_GBL | BMR_BIG_ENDIAN, &priv->ucc_pram->rstate); + iowrite32be(BMR_GBL | BMR_BIG_ENDIAN, &priv->ucc_pram->tstate); + + /* Set C_MASK, C_PRES for 16bit CRC */ + iowrite32be(CRC_16BIT_MASK, &priv->ucc_pram->c_mask); + iowrite32be(CRC_16BIT_PRES, &priv->ucc_pram->c_pres); + + iowrite16be(MAX_FRAME_LENGTH, &priv->ucc_pram->mflr); + iowrite16be(DEFAULT_RFTHR, &priv->ucc_pram->rfthr); + iowrite16be(DEFAULT_RFTHR, &priv->ucc_pram->rfcnt); + iowrite16be(DEFAULT_ADDR_MASK, &priv->ucc_pram->hmask); + iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr1); + iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr2); + iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr3); + iowrite16be(DEFAULT_HDLC_ADDR, &priv->ucc_pram->haddr4); + + /* Get BD buffer */ + bd_buffer = dma_zalloc_coherent(priv->dev, + (RX_BD_RING_LEN + TX_BD_RING_LEN) * + MAX_RX_BUF_LENGTH, + &bd_dma_addr, GFP_KERNEL); + + if (!bd_buffer) { + dev_err(priv->dev, "Could not allocate buffer descriptors\n"); + ret = -ENOMEM; + goto free_tiptr; + } + + priv->rx_buffer = bd_buffer; + priv->tx_buffer = bd_buffer + RX_BD_RING_LEN * MAX_RX_BUF_LENGTH; + + priv->dma_rx_addr = bd_dma_addr; + priv->dma_tx_addr = bd_dma_addr + RX_BD_RING_LEN * MAX_RX_BUF_LENGTH; + + for (i = 0; i < RX_BD_RING_LEN; i++) { + if (i < (RX_BD_RING_LEN - 1)) + bd_status = R_E_S | R_I_S; + else + bd_status = R_E_S | R_I_S | R_W_S; + + iowrite16be(bd_status, &priv->rx_bd_base[i].status); + iowrite32be(priv->dma_rx_addr + i * MAX_RX_BUF_LENGTH, + &priv->rx_bd_base[i].buf); + } + + for (i = 0; i < TX_BD_RING_LEN; i++) { + if (i < (TX_BD_RING_LEN - 1)) + bd_status = T_I_S | T_TC_S; + else + bd_status = T_I_S | T_TC_S | T_W_S; + + iowrite16be(bd_status, &priv->tx_bd_base[i].status); + iowrite32be(priv->dma_tx_addr + i * MAX_RX_BUF_LENGTH, + &priv->tx_bd_base[i].buf); + } + + return 0; + +free_tiptr: + qe_muram_free(tiptr); +free_riptr: + qe_muram_free(riptr); +free_tx_skbuff: + kfree(priv->tx_skbuff); +free_rx_skbuff: + kfree(priv->rx_skbuff); +free_ucc_pram: + qe_muram_free(priv->ucc_pram_offset); +free_tx_bd: + dma_free_coherent(priv->dev, + TX_BD_RING_LEN * sizeof(struct qe_bd), + priv->tx_bd_base, priv->dma_tx_bd); +free_rx_bd: + dma_free_coherent(priv->dev, + RX_BD_RING_LEN * sizeof(struct qe_bd), + priv->rx_bd_base, priv->dma_rx_bd); +free_uccf: + ucc_fast_free(priv->uccf); + + return ret; +} + +static netdev_tx_t ucc_hdlc_tx(struct sk_buff *skb, struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct ucc_hdlc_private *priv = (struct ucc_hdlc_private *)hdlc->priv; + struct qe_bd __iomem *bd; + u16 bd_status; + unsigned long flags; + u16 *proto_head; + + switch (dev->type) { + case ARPHRD_RAWHDLC: + if (skb_headroom(skb) < HDLC_HEAD_LEN) { + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + netdev_err(dev, "No enough space for hdlc head\n"); + return -ENOMEM; + } + + skb_push(skb, HDLC_HEAD_LEN); + + proto_head = (u16 *)skb->data; + *proto_head = htons(DEFAULT_HDLC_HEAD); + + dev->stats.tx_bytes += skb->len; + break; + + case ARPHRD_PPP: + proto_head = (u16 *)skb->data; + if (*proto_head != htons(DEFAULT_PPP_HEAD)) { + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + netdev_err(dev, "Wrong ppp header\n"); + return -ENOMEM; + } + + dev->stats.tx_bytes += skb->len; + break; + + default: + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + return -ENOMEM; + } + spin_lock_irqsave(&priv->lock, flags); + + /* Start from the next BD that should be filled */ + bd = priv->curtx_bd; + bd_status = ioread16be(&bd->status); + /* Save the skb pointer so we can free it later */ + priv->tx_skbuff[priv->skb_curtx] = skb; + + /* Update the current skb pointer (wrapping if this was the last) */ + priv->skb_curtx = + (priv->skb_curtx + 1) & TX_RING_MOD_MASK(TX_BD_RING_LEN); + + /* copy skb data to tx buffer for sdma processing */ + memcpy(priv->tx_buffer + (be32_to_cpu(bd->buf) - priv->dma_tx_addr), + skb->data, skb->len); + + /* set bd status and length */ + bd_status = (bd_status & T_W_S) | T_R_S | T_I_S | T_L_S | T_TC_S; + + iowrite16be(skb->len, &bd->length); + iowrite16be(bd_status, &bd->status); + + /* Move to next BD in the ring */ + if (!(bd_status & T_W_S)) + bd += 1; + else + bd = priv->tx_bd_base; + + if (bd == priv->dirty_tx) { + if (!netif_queue_stopped(dev)) + netif_stop_queue(dev); + } + + priv->curtx_bd = bd; + + spin_unlock_irqrestore(&priv->lock, flags); + + return NETDEV_TX_OK; +} + +static int hdlc_tx_done(struct ucc_hdlc_private *priv) +{ + /* Start from the next BD that should be filled */ + struct net_device *dev = priv->ndev; + struct qe_bd *bd; /* BD pointer */ + u16 bd_status; + + bd = priv->dirty_tx; + bd_status = ioread16be(&bd->status); + + /* Normal processing. */ + while ((bd_status & T_R_S) == 0) { + struct sk_buff *skb; + + /* BD contains already transmitted buffer. */ + /* Handle the transmitted buffer and release */ + /* the BD to be used with the current frame */ + + skb = priv->tx_skbuff[priv->skb_dirtytx]; + if (!skb) + break; + dev->stats.tx_packets++; + memset(priv->tx_buffer + + (be32_to_cpu(bd->buf) - priv->dma_tx_addr), + 0, skb->len); + dev_kfree_skb_irq(skb); + + priv->tx_skbuff[priv->skb_dirtytx] = NULL; + priv->skb_dirtytx = + (priv->skb_dirtytx + + 1) & TX_RING_MOD_MASK(TX_BD_RING_LEN); + + /* We freed a buffer, so now we can restart transmission */ + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + + /* Advance the confirmation BD pointer */ + if (!(bd_status & T_W_S)) + bd += 1; + else + bd = priv->tx_bd_base; + bd_status = ioread16be(&bd->status); + } + priv->dirty_tx = bd; + + return 0; +} + +static int hdlc_rx_done(struct ucc_hdlc_private *priv, int rx_work_limit) +{ + struct net_device *dev = priv->ndev; + struct sk_buff *skb = NULL; + hdlc_device *hdlc = dev_to_hdlc(dev); + struct qe_bd *bd; + u16 bd_status; + u16 length, howmany = 0; + u8 *bdbuffer; + + bd = priv->currx_bd; + bd_status = ioread16be(&bd->status); + + /* while there are received buffers and BD is full (~R_E) */ + while (!((bd_status & (R_E_S)) || (--rx_work_limit < 0))) { + if (bd_status & R_OV_S) + dev->stats.rx_over_errors++; + if (bd_status & R_CR_S) { + dev->stats.rx_crc_errors++; + dev->stats.rx_dropped++; + goto recycle; + } + bdbuffer = priv->rx_buffer + + (priv->currx_bdnum * MAX_RX_BUF_LENGTH); + length = ioread16be(&bd->length); + + switch (dev->type) { + case ARPHRD_RAWHDLC: + bdbuffer += HDLC_HEAD_LEN; + length -= (HDLC_HEAD_LEN + HDLC_CRC_SIZE); + + skb = dev_alloc_skb(length); + if (!skb) { + dev->stats.rx_dropped++; + return -ENOMEM; + } + + skb_put(skb, length); + skb->len = length; + skb->dev = dev; + memcpy(skb->data, bdbuffer, length); + break; + + case ARPHRD_PPP: + length -= HDLC_CRC_SIZE; + + skb = dev_alloc_skb(length); + if (!skb) { + dev->stats.rx_dropped++; + return -ENOMEM; + } + + skb_put(skb, length); + skb->len = length; + skb->dev = dev; + memcpy(skb->data, bdbuffer, length); + break; + } + + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + howmany++; + if (hdlc->proto) + skb->protocol = hdlc_type_trans(skb, dev); + netif_receive_skb(skb); + +recycle: + iowrite16be(bd_status | R_E_S | R_I_S, &bd->status); + + /* update to point at the next bd */ + if (bd_status & R_W_S) { + priv->currx_bdnum = 0; + bd = priv->rx_bd_base; + } else { + if (priv->currx_bdnum < (RX_BD_RING_LEN - 1)) + priv->currx_bdnum += 1; + else + priv->currx_bdnum = RX_BD_RING_LEN - 1; + + bd += 1; + } + + bd_status = ioread16be(&bd->status); + } + + priv->currx_bd = bd; + return howmany; +} + +static int ucc_hdlc_poll(struct napi_struct *napi, int budget) +{ + struct ucc_hdlc_private *priv = container_of(napi, + struct ucc_hdlc_private, + napi); + int howmany; + + /* Tx event processing */ + spin_lock(&priv->lock); + hdlc_tx_done(priv); + spin_unlock(&priv->lock); + + howmany = 0; + howmany += hdlc_rx_done(priv, budget - howmany); + + if (howmany < budget) { + napi_complete_done(napi, howmany); + qe_setbits32(priv->uccf->p_uccm, + (UCCE_HDLC_RX_EVENTS | UCCE_HDLC_TX_EVENTS) << 16); + } + + return howmany; +} + +static irqreturn_t ucc_hdlc_irq_handler(int irq, void *dev_id) +{ + struct ucc_hdlc_private *priv = (struct ucc_hdlc_private *)dev_id; + struct net_device *dev = priv->ndev; + struct ucc_fast_private *uccf; + struct ucc_tdm_info *ut_info; + u32 ucce; + u32 uccm; + + ut_info = priv->ut_info; + uccf = priv->uccf; + + ucce = ioread32be(uccf->p_ucce); + uccm = ioread32be(uccf->p_uccm); + ucce &= uccm; + iowrite32be(ucce, uccf->p_ucce); + if (!ucce) + return IRQ_NONE; + + if ((ucce >> 16) & (UCCE_HDLC_RX_EVENTS | UCCE_HDLC_TX_EVENTS)) { + if (napi_schedule_prep(&priv->napi)) { + uccm &= ~((UCCE_HDLC_RX_EVENTS | UCCE_HDLC_TX_EVENTS) + << 16); + iowrite32be(uccm, uccf->p_uccm); + __napi_schedule(&priv->napi); + } + } + + /* Errors and other events */ + if (ucce >> 16 & UCC_HDLC_UCCE_BSY) + dev->stats.rx_errors++; + if (ucce >> 16 & UCC_HDLC_UCCE_TXE) + dev->stats.tx_errors++; + + return IRQ_HANDLED; +} + +static int uhdlc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + const size_t size = sizeof(te1_settings); + te1_settings line; + struct ucc_hdlc_private *priv = netdev_priv(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_E1; + if (ifr->ifr_settings.size < size) { + ifr->ifr_settings.size = size; /* data size wanted */ + return -ENOBUFS; + } + memset(&line, 0, sizeof(line)); + line.clock_type = priv->clocking; + + if (copy_to_user(ifr->ifr_settings.ifs_ifsu.sync, &line, size)) + return -EFAULT; + return 0; + + default: + return hdlc_ioctl(dev, ifr, cmd); + } +} + +static int uhdlc_open(struct net_device *dev) +{ + u32 cecr_subblock; + hdlc_device *hdlc = dev_to_hdlc(dev); + struct ucc_hdlc_private *priv = hdlc->priv; + struct ucc_tdm *utdm = priv->utdm; + + if (priv->hdlc_busy != 1) { + if (request_irq(priv->ut_info->uf_info.irq, + ucc_hdlc_irq_handler, 0, "hdlc", priv)) + return -ENODEV; + + cecr_subblock = ucc_fast_get_qe_cr_subblock( + priv->ut_info->uf_info.ucc_num); + + qe_issue_cmd(QE_INIT_TX_RX, cecr_subblock, + QE_CR_PROTOCOL_UNSPECIFIED, 0); + + ucc_fast_enable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX); + + /* Enable the TDM port */ + if (priv->tsa) + utdm->si_regs->siglmr1_h |= (0x1 << utdm->tdm_port); + + priv->hdlc_busy = 1; + netif_device_attach(priv->ndev); + napi_enable(&priv->napi); + netif_start_queue(dev); + hdlc_open(dev); + } + + return 0; +} + +static void uhdlc_memclean(struct ucc_hdlc_private *priv) +{ + qe_muram_free(priv->ucc_pram->riptr); + qe_muram_free(priv->ucc_pram->tiptr); + + if (priv->rx_bd_base) { + dma_free_coherent(priv->dev, + RX_BD_RING_LEN * sizeof(struct qe_bd), + priv->rx_bd_base, priv->dma_rx_bd); + + priv->rx_bd_base = NULL; + priv->dma_rx_bd = 0; + } + + if (priv->tx_bd_base) { + dma_free_coherent(priv->dev, + TX_BD_RING_LEN * sizeof(struct qe_bd), + priv->tx_bd_base, priv->dma_tx_bd); + + priv->tx_bd_base = NULL; + priv->dma_tx_bd = 0; + } + + if (priv->ucc_pram) { + qe_muram_free(priv->ucc_pram_offset); + priv->ucc_pram = NULL; + priv->ucc_pram_offset = 0; + } + + kfree(priv->rx_skbuff); + priv->rx_skbuff = NULL; + + kfree(priv->tx_skbuff); + priv->tx_skbuff = NULL; + + if (priv->uf_regs) { + iounmap(priv->uf_regs); + priv->uf_regs = NULL; + } + + if (priv->uccf) { + ucc_fast_free(priv->uccf); + priv->uccf = NULL; + } + + if (priv->rx_buffer) { + dma_free_coherent(priv->dev, + RX_BD_RING_LEN * MAX_RX_BUF_LENGTH, + priv->rx_buffer, priv->dma_rx_addr); + priv->rx_buffer = NULL; + priv->dma_rx_addr = 0; + } + + if (priv->tx_buffer) { + dma_free_coherent(priv->dev, + TX_BD_RING_LEN * MAX_RX_BUF_LENGTH, + priv->tx_buffer, priv->dma_tx_addr); + priv->tx_buffer = NULL; + priv->dma_tx_addr = 0; + } +} + +static int uhdlc_close(struct net_device *dev) +{ + struct ucc_hdlc_private *priv = dev_to_hdlc(dev)->priv; + struct ucc_tdm *utdm = priv->utdm; + u32 cecr_subblock; + + napi_disable(&priv->napi); + cecr_subblock = ucc_fast_get_qe_cr_subblock( + priv->ut_info->uf_info.ucc_num); + + qe_issue_cmd(QE_GRACEFUL_STOP_TX, cecr_subblock, + (u8)QE_CR_PROTOCOL_UNSPECIFIED, 0); + qe_issue_cmd(QE_CLOSE_RX_BD, cecr_subblock, + (u8)QE_CR_PROTOCOL_UNSPECIFIED, 0); + + if (priv->tsa) + utdm->si_regs->siglmr1_h &= ~(0x1 << utdm->tdm_port); + + ucc_fast_disable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX); + + free_irq(priv->ut_info->uf_info.irq, priv); + netif_stop_queue(dev); + priv->hdlc_busy = 0; + + return 0; +} + +static int ucc_hdlc_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + struct ucc_hdlc_private *priv = dev_to_hdlc(dev)->priv; + + if (encoding != ENCODING_NRZ && + encoding != ENCODING_NRZI) + return -EINVAL; + + if (parity != PARITY_NONE && + parity != PARITY_CRC32_PR1_CCITT && + parity != PARITY_CRC16_PR1_CCITT) + return -EINVAL; + + priv->encoding = encoding; + priv->parity = parity; + + return 0; +} + +#ifdef CONFIG_PM +static void store_clk_config(struct ucc_hdlc_private *priv) +{ + struct qe_mux *qe_mux_reg = &qe_immr->qmx; + + /* store si clk */ + priv->cmxsi1cr_h = ioread32be(&qe_mux_reg->cmxsi1cr_h); + priv->cmxsi1cr_l = ioread32be(&qe_mux_reg->cmxsi1cr_l); + + /* store si sync */ + priv->cmxsi1syr = ioread32be(&qe_mux_reg->cmxsi1syr); + + /* store ucc clk */ + memcpy_fromio(priv->cmxucr, qe_mux_reg->cmxucr, 4 * sizeof(u32)); +} + +static void resume_clk_config(struct ucc_hdlc_private *priv) +{ + struct qe_mux *qe_mux_reg = &qe_immr->qmx; + + memcpy_toio(qe_mux_reg->cmxucr, priv->cmxucr, 4 * sizeof(u32)); + + iowrite32be(priv->cmxsi1cr_h, &qe_mux_reg->cmxsi1cr_h); + iowrite32be(priv->cmxsi1cr_l, &qe_mux_reg->cmxsi1cr_l); + + iowrite32be(priv->cmxsi1syr, &qe_mux_reg->cmxsi1syr); +} + +static int uhdlc_suspend(struct device *dev) +{ + struct ucc_hdlc_private *priv = dev_get_drvdata(dev); + struct ucc_tdm_info *ut_info; + struct ucc_fast __iomem *uf_regs; + + if (!priv) + return -EINVAL; + + if (!netif_running(priv->ndev)) + return 0; + + netif_device_detach(priv->ndev); + napi_disable(&priv->napi); + + ut_info = priv->ut_info; + uf_regs = priv->uf_regs; + + /* backup gumr guemr*/ + priv->gumr = ioread32be(&uf_regs->gumr); + priv->guemr = ioread8(&uf_regs->guemr); + + priv->ucc_pram_bak = kmalloc(sizeof(*priv->ucc_pram_bak), + GFP_KERNEL); + if (!priv->ucc_pram_bak) + return -ENOMEM; + + /* backup HDLC parameter */ + memcpy_fromio(priv->ucc_pram_bak, priv->ucc_pram, + sizeof(struct ucc_hdlc_param)); + + /* store the clk configuration */ + store_clk_config(priv); + + /* save power */ + ucc_fast_disable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX); + + return 0; +} + +static int uhdlc_resume(struct device *dev) +{ + struct ucc_hdlc_private *priv = dev_get_drvdata(dev); + struct ucc_tdm *utdm; + struct ucc_tdm_info *ut_info; + struct ucc_fast __iomem *uf_regs; + struct ucc_fast_private *uccf; + struct ucc_fast_info *uf_info; + int ret, i; + u32 cecr_subblock; + u16 bd_status; + + if (!priv) + return -EINVAL; + + if (!netif_running(priv->ndev)) + return 0; + + utdm = priv->utdm; + ut_info = priv->ut_info; + uf_info = &ut_info->uf_info; + uf_regs = priv->uf_regs; + uccf = priv->uccf; + + /* restore gumr guemr */ + iowrite8(priv->guemr, &uf_regs->guemr); + iowrite32be(priv->gumr, &uf_regs->gumr); + + /* Set Virtual Fifo registers */ + iowrite16be(uf_info->urfs, &uf_regs->urfs); + iowrite16be(uf_info->urfet, &uf_regs->urfet); + iowrite16be(uf_info->urfset, &uf_regs->urfset); + iowrite16be(uf_info->utfs, &uf_regs->utfs); + iowrite16be(uf_info->utfet, &uf_regs->utfet); + iowrite16be(uf_info->utftt, &uf_regs->utftt); + /* utfb, urfb are offsets from MURAM base */ + iowrite32be(uccf->ucc_fast_tx_virtual_fifo_base_offset, &uf_regs->utfb); + iowrite32be(uccf->ucc_fast_rx_virtual_fifo_base_offset, &uf_regs->urfb); + + /* Rx Tx and sync clock routing */ + resume_clk_config(priv); + + iowrite32be(uf_info->uccm_mask, &uf_regs->uccm); + iowrite32be(0xffffffff, &uf_regs->ucce); + + ucc_fast_disable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX); + + /* rebuild SIRAM */ + if (priv->tsa) + ucc_tdm_init(priv->utdm, priv->ut_info); + + /* Write to QE CECR, UCCx channel to Stop Transmission */ + cecr_subblock = ucc_fast_get_qe_cr_subblock(uf_info->ucc_num); + ret = qe_issue_cmd(QE_STOP_TX, cecr_subblock, + (u8)QE_CR_PROTOCOL_UNSPECIFIED, 0); + + /* Set UPSMR normal mode */ + iowrite32be(0, &uf_regs->upsmr); + + /* init parameter base */ + cecr_subblock = ucc_fast_get_qe_cr_subblock(uf_info->ucc_num); + ret = qe_issue_cmd(QE_ASSIGN_PAGE_TO_DEVICE, cecr_subblock, + QE_CR_PROTOCOL_UNSPECIFIED, priv->ucc_pram_offset); + + priv->ucc_pram = (struct ucc_hdlc_param __iomem *) + qe_muram_addr(priv->ucc_pram_offset); + + /* restore ucc parameter */ + memcpy_toio(priv->ucc_pram, priv->ucc_pram_bak, + sizeof(struct ucc_hdlc_param)); + kfree(priv->ucc_pram_bak); + + /* rebuild BD entry */ + for (i = 0; i < RX_BD_RING_LEN; i++) { + if (i < (RX_BD_RING_LEN - 1)) + bd_status = R_E_S | R_I_S; + else + bd_status = R_E_S | R_I_S | R_W_S; + + iowrite16be(bd_status, &priv->rx_bd_base[i].status); + iowrite32be(priv->dma_rx_addr + i * MAX_RX_BUF_LENGTH, + &priv->rx_bd_base[i].buf); + } + + for (i = 0; i < TX_BD_RING_LEN; i++) { + if (i < (TX_BD_RING_LEN - 1)) + bd_status = T_I_S | T_TC_S; + else + bd_status = T_I_S | T_TC_S | T_W_S; + + iowrite16be(bd_status, &priv->tx_bd_base[i].status); + iowrite32be(priv->dma_tx_addr + i * MAX_RX_BUF_LENGTH, + &priv->tx_bd_base[i].buf); + } + + /* if hdlc is busy enable TX and RX */ + if (priv->hdlc_busy == 1) { + cecr_subblock = ucc_fast_get_qe_cr_subblock( + priv->ut_info->uf_info.ucc_num); + + qe_issue_cmd(QE_INIT_TX_RX, cecr_subblock, + (u8)QE_CR_PROTOCOL_UNSPECIFIED, 0); + + ucc_fast_enable(priv->uccf, COMM_DIR_RX | COMM_DIR_TX); + + /* Enable the TDM port */ + if (priv->tsa) + utdm->si_regs->siglmr1_h |= (0x1 << utdm->tdm_port); + } + + napi_enable(&priv->napi); + netif_device_attach(priv->ndev); + + return 0; +} + +static const struct dev_pm_ops uhdlc_pm_ops = { + .suspend = uhdlc_suspend, + .resume = uhdlc_resume, + .freeze = uhdlc_suspend, + .thaw = uhdlc_resume, +}; + +#define HDLC_PM_OPS (&uhdlc_pm_ops) + +#else + +#define HDLC_PM_OPS NULL + +#endif +static const struct net_device_ops uhdlc_ops = { + .ndo_open = uhdlc_open, + .ndo_stop = uhdlc_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = uhdlc_ioctl, +}; + +static int ucc_hdlc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct ucc_hdlc_private *uhdlc_priv = NULL; + struct ucc_tdm_info *ut_info; + struct ucc_tdm *utdm = NULL; + struct resource res; + struct net_device *dev; + hdlc_device *hdlc; + int ucc_num; + const char *sprop; + int ret; + u32 val; + + ret = of_property_read_u32_index(np, "cell-index", 0, &val); + if (ret) { + dev_err(&pdev->dev, "Invalid ucc property\n"); + return -ENODEV; + } + + ucc_num = val - 1; + if ((ucc_num > 3) || (ucc_num < 0)) { + dev_err(&pdev->dev, ": Invalid UCC num\n"); + return -EINVAL; + } + + memcpy(&utdm_info[ucc_num], &utdm_primary_info, + sizeof(utdm_primary_info)); + + ut_info = &utdm_info[ucc_num]; + ut_info->uf_info.ucc_num = ucc_num; + + sprop = of_get_property(np, "rx-clock-name", NULL); + if (sprop) { + ut_info->uf_info.rx_clock = qe_clock_source(sprop); + if ((ut_info->uf_info.rx_clock < QE_CLK_NONE) || + (ut_info->uf_info.rx_clock > QE_CLK24)) { + dev_err(&pdev->dev, "Invalid rx-clock-name property\n"); + return -EINVAL; + } + } else { + dev_err(&pdev->dev, "Invalid rx-clock-name property\n"); + return -EINVAL; + } + + sprop = of_get_property(np, "tx-clock-name", NULL); + if (sprop) { + ut_info->uf_info.tx_clock = qe_clock_source(sprop); + if ((ut_info->uf_info.tx_clock < QE_CLK_NONE) || + (ut_info->uf_info.tx_clock > QE_CLK24)) { + dev_err(&pdev->dev, "Invalid tx-clock-name property\n"); + return -EINVAL; + } + } else { + dev_err(&pdev->dev, "Invalid tx-clock-name property\n"); + return -EINVAL; + } + + ret = of_address_to_resource(np, 0, &res); + if (ret) + return -EINVAL; + + ut_info->uf_info.regs = res.start; + ut_info->uf_info.irq = irq_of_parse_and_map(np, 0); + + uhdlc_priv = kzalloc(sizeof(*uhdlc_priv), GFP_KERNEL); + if (!uhdlc_priv) { + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, uhdlc_priv); + uhdlc_priv->dev = &pdev->dev; + uhdlc_priv->ut_info = ut_info; + + if (of_get_property(np, "fsl,tdm-interface", NULL)) + uhdlc_priv->tsa = 1; + + if (of_get_property(np, "fsl,ucc-internal-loopback", NULL)) + uhdlc_priv->loopback = 1; + + if (of_get_property(np, "fsl,hdlc-bus", NULL)) + uhdlc_priv->hdlc_bus = 1; + + if (uhdlc_priv->tsa == 1) { + utdm = kzalloc(sizeof(*utdm), GFP_KERNEL); + if (!utdm) { + ret = -ENOMEM; + dev_err(&pdev->dev, "No mem to alloc ucc tdm data\n"); + goto free_uhdlc_priv; + } + uhdlc_priv->utdm = utdm; + ret = ucc_of_parse_tdm(np, utdm, ut_info); + if (ret) + goto free_utdm; + } + + ret = uhdlc_init(uhdlc_priv); + if (ret) { + dev_err(&pdev->dev, "Failed to init uhdlc\n"); + goto free_utdm; + } + + dev = alloc_hdlcdev(uhdlc_priv); + if (!dev) { + ret = -ENOMEM; + pr_err("ucc_hdlc: unable to allocate memory\n"); + goto undo_uhdlc_init; + } + + uhdlc_priv->ndev = dev; + hdlc = dev_to_hdlc(dev); + dev->tx_queue_len = 16; + dev->netdev_ops = &uhdlc_ops; + hdlc->attach = ucc_hdlc_attach; + hdlc->xmit = ucc_hdlc_tx; + netif_napi_add(dev, &uhdlc_priv->napi, ucc_hdlc_poll, 32); + if (register_hdlc_device(dev)) { + ret = -ENOBUFS; + pr_err("ucc_hdlc: unable to register hdlc device\n"); + goto free_dev; + } + + return 0; + +free_dev: + free_netdev(dev); +undo_uhdlc_init: +free_utdm: + if (uhdlc_priv->tsa) + kfree(utdm); +free_uhdlc_priv: + kfree(uhdlc_priv); + return ret; +} + +static int ucc_hdlc_remove(struct platform_device *pdev) +{ + struct ucc_hdlc_private *priv = dev_get_drvdata(&pdev->dev); + + uhdlc_memclean(priv); + + if (priv->utdm->si_regs) { + iounmap(priv->utdm->si_regs); + priv->utdm->si_regs = NULL; + } + + if (priv->utdm->siram) { + iounmap(priv->utdm->siram); + priv->utdm->siram = NULL; + } + kfree(priv); + + dev_info(&pdev->dev, "UCC based hdlc module removed\n"); + + return 0; +} + +static const struct of_device_id fsl_ucc_hdlc_of_match[] = { + { + .compatible = "fsl,ucc-hdlc", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, fsl_ucc_hdlc_of_match); + +static struct platform_driver ucc_hdlc_driver = { + .probe = ucc_hdlc_probe, + .remove = ucc_hdlc_remove, + .driver = { + .name = DRV_NAME, + .pm = HDLC_PM_OPS, + .of_match_table = fsl_ucc_hdlc_of_match, + }, +}; + +module_platform_driver(ucc_hdlc_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wan/fsl_ucc_hdlc.h b/drivers/net/wan/fsl_ucc_hdlc.h new file mode 100644 index 000000000..c21134c1f --- /dev/null +++ b/drivers/net/wan/fsl_ucc_hdlc.h @@ -0,0 +1,148 @@ +/* Freescale QUICC Engine HDLC Device Driver + * + * Copyright 2014 Freescale Semiconductor 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. + */ + +#ifndef _UCC_HDLC_H_ +#define _UCC_HDLC_H_ + +#include <linux/kernel.h> +#include <linux/list.h> + +#include <soc/fsl/qe/immap_qe.h> +#include <soc/fsl/qe/qe.h> + +#include <soc/fsl/qe/ucc.h> +#include <soc/fsl/qe/ucc_fast.h> + +/* UCC HDLC event register */ +#define UCCE_HDLC_RX_EVENTS \ +(UCC_HDLC_UCCE_RXF | UCC_HDLC_UCCE_RXB | UCC_HDLC_UCCE_BSY) +#define UCCE_HDLC_TX_EVENTS (UCC_HDLC_UCCE_TXB | UCC_HDLC_UCCE_TXE) + +struct ucc_hdlc_param { + __be16 riptr; + __be16 tiptr; + __be16 res0; + __be16 mrblr; + __be32 rstate; + __be32 rbase; + __be16 rbdstat; + __be16 rbdlen; + __be32 rdptr; + __be32 tstate; + __be32 tbase; + __be16 tbdstat; + __be16 tbdlen; + __be32 tdptr; + __be32 rbptr; + __be32 tbptr; + __be32 rcrc; + __be32 res1; + __be32 tcrc; + __be32 res2; + __be32 res3; + __be32 c_mask; + __be32 c_pres; + __be16 disfc; + __be16 crcec; + __be16 abtsc; + __be16 nmarc; + __be32 max_cnt; + __be16 mflr; + __be16 rfthr; + __be16 rfcnt; + __be16 hmask; + __be16 haddr1; + __be16 haddr2; + __be16 haddr3; + __be16 haddr4; + __be16 ts_tmp; + __be16 tmp_mb; +}; + +struct ucc_hdlc_private { + struct ucc_tdm *utdm; + struct ucc_tdm_info *ut_info; + struct ucc_fast_private *uccf; + struct device *dev; + struct net_device *ndev; + struct napi_struct napi; + struct ucc_fast __iomem *uf_regs; /* UCC Fast registers */ + struct ucc_hdlc_param __iomem *ucc_pram; + u16 tsa; + bool hdlc_busy; + bool loopback; + bool hdlc_bus; + + u8 *tx_buffer; + u8 *rx_buffer; + dma_addr_t dma_tx_addr; + dma_addr_t dma_rx_addr; + + struct qe_bd *tx_bd_base; + struct qe_bd *rx_bd_base; + dma_addr_t dma_tx_bd; + dma_addr_t dma_rx_bd; + struct qe_bd *curtx_bd; + struct qe_bd *currx_bd; + struct qe_bd *dirty_tx; + u16 currx_bdnum; + + struct sk_buff **tx_skbuff; + struct sk_buff **rx_skbuff; + u16 skb_curtx; + u16 skb_currx; + unsigned short skb_dirtytx; + + unsigned short tx_ring_size; + unsigned short rx_ring_size; + u32 ucc_pram_offset; + + unsigned short encoding; + unsigned short parity; + u32 clocking; + spinlock_t lock; /* lock for Tx BD and Tx buffer */ +#ifdef CONFIG_PM + struct ucc_hdlc_param *ucc_pram_bak; + u32 gumr; + u8 guemr; + u32 cmxsi1cr_l, cmxsi1cr_h; + u32 cmxsi1syr; + u32 cmxucr[4]; +#endif +}; + +#define TX_BD_RING_LEN 0x10 +#define RX_BD_RING_LEN 0x20 +#define RX_CLEAN_MAX 0x10 +#define NUM_OF_BUF 4 +#define MAX_RX_BUF_LENGTH (48 * 0x20) +#define MAX_FRAME_LENGTH (MAX_RX_BUF_LENGTH + 8) +#define ALIGNMENT_OF_UCC_HDLC_PRAM 64 +#define SI_BANK_SIZE 128 +#define MAX_HDLC_NUM 4 +#define HDLC_HEAD_LEN 2 +#define HDLC_CRC_SIZE 2 +#define TX_RING_MOD_MASK(size) (size - 1) +#define RX_RING_MOD_MASK(size) (size - 1) + +#define HDLC_HEAD_MASK 0x0000 +#define DEFAULT_HDLC_HEAD 0xff44 +#define DEFAULT_ADDR_MASK 0x00ff +#define DEFAULT_HDLC_ADDR 0x00ff + +#define BMR_GBL 0x20000000 +#define BMR_BIG_ENDIAN 0x10000000 +#define CRC_16BIT_MASK 0x0000F0B8 +#define CRC_16BIT_PRES 0x0000FFFF +#define DEFAULT_RFTHR 1 + +#define DEFAULT_PPP_HEAD 0xff03 + +#endif diff --git a/drivers/net/wan/hd64570.c b/drivers/net/wan/hd64570.c new file mode 100644 index 000000000..166696d2c --- /dev/null +++ b/drivers/net/wan/hd64570.c @@ -0,0 +1,719 @@ +/* + * Hitachi SCA HD64570 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. + * + * Source of information: Hitachi HD64570 SCA 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/bitops.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/hdlc.h> +#include <linux/in.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/types.h> +#include <asm/io.h> +#include <linux/uaccess.h> +#include "hd64570.h" + +#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) + + +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; + 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); + + 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 bits */ + 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 inline void sca_set_carrier(port_t *port) +{ + if (!(sca_in(get_msci(port) + ST3, port_to_card(port)) & ST3_DCD)) { +#ifdef DEBUG_LINK + printk(KERN_DEBUG "%s: sca_set_carrier on\n", + port_to_dev(port)->name); +#endif + netif_carrier_on(port_to_dev(port)); + } else { +#ifdef DEBUG_LINK + printk(KERN_DEBUG "%s: sca_set_carrier off\n", + port_to_dev(port)->name); +#endif + netif_carrier_off(port_to_dev(port)); + } +} + + +static void sca_init_port(port_t *port) +{ + card_t *card = port_to_card(port); + int transmit, i; + + port->rxin = 0; + port->txin = 0; + port->txlast = 0; + +#ifndef PAGE0_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); + + writew(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); + + /* current desc addr */ + sca_out(0, dmac + CPB, card); /* pointer base */ + sca_outw(desc_offset(port, 0, transmit), dmac + CDAL, card); + if (!transmit) + sca_outw(desc_offset(port, buffs - 1, transmit), + dmac + EDAL, card); + else + sca_outw(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); + } + } + sca_set_carrier(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) { + /* TX Underrun error detected */ + port_to_dev(port)->stats.tx_errors++; + port_to_dev(port)->stats.tx_fifo_errors++; + } + + if (stat & ST1_CDCD) + sca_set_carrier(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 sk_buff *skb; + u16 len; + u32 buff; + u32 maxlen; + u8 page; + + len = readw(&desc->len); + skb = dev_alloc_skb(len); + if (!skb) { + dev->stats.rx_dropped++; + return; + } + + buff = buffer_offset(port, rxin, 0); + 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 + memcpy_fromio(skb->data, winbase(card) + buff, len); + +#ifndef PAGE0_ALWAYS_MAPPED + openwin(card, 0); /* select pkt_desc table page back */ +#endif + skb_put(skb, len); +#ifdef DEBUG_PKT + printk(KERN_DEBUG "%s RX(%i):", dev->name, skb->len); + debug_frame(skb); +#endif + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + skb->protocol = hdlc_type_trans(skb, dev); + netif_rx(skb); +} + + +/* Receive DMA interrupt service */ +static inline void sca_rx_intr(port_t *port) +{ + struct net_device *dev = port_to_dev(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 */ + + /* 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) + /* Dropped one or more frames */ + dev->stats.rx_over_errors++; + + while (1) { + u32 desc_off = desc_offset(port, port->rxin, 0); + pkt_desc __iomem *desc; + u32 cda = sca_inw(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) { + dev->stats.rx_errors++; + if (stat & ST_RX_OVERRUN) + dev->stats.rx_fifo_errors++; + else if ((stat & (ST_RX_SHORT | ST_RX_ABORT | + ST_RX_RESBIT)) || port->rxpart) + dev->stats.rx_frame_errors++; + else if (stat & ST_RX_CRC) + dev->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_outw(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); + 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_inw(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); + dev->stats.tx_packets++; + dev->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) +{ + card_t *card = dev_id; + int i; + u8 stat; + int handled = 0; + u8 page = sca_get_page(card); + + 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); + } + } + } + + openwin(card, page); /* Restore original page */ + 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*/ + sca_out(port->tmc, msci + TMC, card); + + /* 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; + case PARITY_CRC16_PR0_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU_0; break; + 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 */ + sca_out(CTL_IDLE, msci + CTL, card); + + /* 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 */ + +/* We're using the following interrupts: + - TXINT (DMAC completed all transmisions, underrun or DCD change) + - all DMA interrupts +*/ + sca_set_carrier(port); + + /* 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); + + sca_out(port->tmc, msci + TMC, card); /* Restore registers */ + 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)); + /* 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); + + 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 && + parity != PARITY_CRC16_PR0_CCITT && + 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; +#ifndef PAGE0_ALWAYS_MAPPED + u8 page = sca_get_page(card); + + openwin(card, 0); +#endif + + printk(KERN_DEBUG "RX ring: CDA=%u EDA=%u DSR=%02X in=%u %sactive", + sca_inw(get_dmac_rx(port) + CDAL, card), + sca_inw(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++) + pr_cont(" %02X", readb(&(desc_address(port, cnt, 0)->stat))); + pr_cont("\n"); + + printk(KERN_DEBUG "TX ring: CDA=%u EDA=%u DSR=%02X in=%u " + "last=%u %sactive", + sca_inw(get_dmac_tx(port) + CDAL, card), + sca_inw(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++) + pr_cont(" %02X", readb(&(desc_address(port, cnt, 1)->stat))); + pr_cont("\n"); + + printk(KERN_DEBUG "MSCI: MD: %02x %02x %02x, ST: %02x %02x %02x %02x," + " 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), + sca_in(get_msci(port) + FST, card), + sca_in(get_msci(port) + CST0, card), + sca_in(get_msci(port) + CST1, card)); + + printk(KERN_DEBUG "ISR: %02x %02x %02x\n", sca_in(ISR0, card), + sca_in(ISR1, card), sca_in(ISR2, card)); + +#ifndef PAGE0_ALWAYS_MAPPED + openwin(card, page); /* Restore original page */ +#endif +} +#endif /* DEBUG_RINGS */ + + +static netdev_tx_t 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; + u8 page; + u32 maxlen; + + spin_lock_irq(&port->lock); + + desc = desc_address(port, port->txin + 1, 1); + BUG_ON(readb(&desc->stat)); /* previous xmit should stop queue */ + +#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; + 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 + memcpy_toio(winbase(card) + buff, skb->data, len); + +#ifndef PAGE0_ALWAYS_MAPPED + openwin(card, 0); /* select pkt_desc table page back */ +#endif + writew(len, &desc->len); + writeb(ST_TX_EOM, &desc->stat); + + port->txin = next_desc(port, port->txin, 1); + sca_outw(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 NETDEV_TX_OK; +} + + +#ifdef NEED_DETECT_RAM +static u32 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; + u32 size = winsize(card); + + openwin(card, (i - 4) / size); /* select last window */ + + do { + i -= 4; + if ((i + 4) % size == 0) + openwin(card, i / size); + writel(i ^ 0x12345678, rambase + i % size); + } while (i > 0); + + for (i = 0; i < ramsize ; i += 4) { + if (i % size == 0) + openwin(card, i / size); + + if (readl(rambase + i % size) != (i ^ 0x12345678)) + break; + } + + return i; +} +#endif /* NEED_DETECT_RAM */ + + +static void 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/hd64570.h b/drivers/net/wan/hd64570.h new file mode 100644 index 000000000..24529996c --- /dev/null +++ b/drivers/net/wan/hd64570.h @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 */ +}__packed pkt_desc; + + +/* Packet Descriptor Status bits */ + +#define ST_TX_EOM 0x80 /* End of frame */ +#define ST_TX_EOT 0x01 /* End of transmission */ + +#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 transmission */ + +#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.c b/drivers/net/wan/hd64572.c new file mode 100644 index 000000000..cff0cfadd --- /dev/null +++ b/drivers/net/wan/hd64572.c @@ -0,0 +1,639 @@ +/* + * Hitachi (now Renesas) SCA-II HD64572 driver for Linux + * + * Copyright (C) 1998-2008 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. + * + * Source of information: HD64572 SCA-II User's Manual + * + * We use the following SCA memory map: + * + * Packet buffer descriptor rings - starting from card->rambase: + * 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 card->rambase + 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/bitops.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/hdlc.h> +#include <linux/in.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/types.h> +#include <asm/io.h> +#include <linux/uaccess.h> +#include "hd64572.h" + +#define NAPI_WEIGHT 16 + +#define get_msci(port) (port->chan ? MSCI1_OFFSET : MSCI0_OFFSET) +#define get_dmac_rx(port) (port->chan ? DMAC1RX_OFFSET : DMAC0RX_OFFSET) +#define get_dmac_tx(port) (port->chan ? DMAC1TX_OFFSET : DMAC0TX_OFFSET) + +#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)) + +static int sca_poll(struct napi_struct *napi, int budget); + +static inline port_t* dev_to_port(struct net_device *dev) +{ + return dev_to_hdlc(dev)->priv; +} + +static inline void enable_intr(port_t *port) +{ + /* enable DMIB and MSCI RXINTA interrupts */ + sca_outl(sca_inl(IER0, port->card) | + (port->chan ? 0x08002200 : 0x00080022), IER0, port->card); +} + +static inline void disable_intr(port_t *port) +{ + sca_outl(sca_inl(IER0, port->card) & + (port->chan ? 0x00FF00FF : 0xFF00FF00), IER0, port->card); +} + +static inline u16 desc_abs_number(port_t *port, u16 desc, int transmit) +{ + u16 rx_buffs = port->card->rx_ring_buffers; + u16 tx_buffs = port->card->tx_ring_buffers; + + desc %= (transmit ? tx_buffs : rx_buffs); // called with "X + 1" etc. + return port->chan * (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 bits */ + return desc_abs_number(port, desc, transmit) * sizeof(pkt_desc); +} + + +static inline pkt_desc __iomem *desc_address(port_t *port, u16 desc, + int transmit) +{ + return (pkt_desc __iomem *)(port->card->rambase + + desc_offset(port, desc, transmit)); +} + + +static inline u32 buffer_offset(port_t *port, u16 desc, int transmit) +{ + return port->card->buff_offset + + desc_abs_number(port, desc, transmit) * (u32)HDLC_MAX_MRU; +} + + +static inline void sca_set_carrier(port_t *port) +{ + if (!(sca_in(get_msci(port) + ST3, port->card) & ST3_DCD)) { +#ifdef DEBUG_LINK + printk(KERN_DEBUG "%s: sca_set_carrier on\n", + port->netdev.name); +#endif + netif_carrier_on(port->netdev); + } else { +#ifdef DEBUG_LINK + printk(KERN_DEBUG "%s: sca_set_carrier off\n", + port->netdev.name); +#endif + netif_carrier_off(port->netdev); + } +} + + +static void sca_init_port(port_t *port) +{ + card_t *card = port->card; + u16 dmac_rx = get_dmac_rx(port), dmac_tx = get_dmac_tx(port); + int transmit, i; + + port->rxin = 0; + port->txin = 0; + port->txlast = 0; + + for (transmit = 0; transmit < 2; transmit++) { + 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); + + writel(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, DSR_RX(port->chan), card); + sca_out(0, DSR_TX(port->chan), card); + + /* software ABORT - to initial state */ + sca_out(DCR_ABORT, DCR_RX(port->chan), card); + sca_out(DCR_ABORT, DCR_TX(port->chan), card); + + /* current desc addr */ + sca_outl(desc_offset(port, 0, 0), dmac_rx + CDAL, card); + sca_outl(desc_offset(port, card->tx_ring_buffers - 1, 0), + dmac_rx + EDAL, card); + sca_outl(desc_offset(port, 0, 1), dmac_tx + CDAL, card); + sca_outl(desc_offset(port, 0, 1), dmac_tx + EDAL, card); + + /* clear frame end interrupt counter */ + sca_out(DCR_CLEAR_EOF, DCR_RX(port->chan), card); + sca_out(DCR_CLEAR_EOF, DCR_TX(port->chan), card); + + /* Receive */ + sca_outw(HDLC_MAX_MRU, dmac_rx + BFLL, card); /* set buffer length */ + sca_out(0x14, DMR_RX(port->chan), card); /* Chain mode, Multi-frame */ + sca_out(DIR_EOME, DIR_RX(port->chan), card); /* enable interrupts */ + sca_out(DSR_DE, DSR_RX(port->chan), card); /* DMA enable */ + + /* Transmit */ + sca_out(0x14, DMR_TX(port->chan), card); /* Chain mode, Multi-frame */ + sca_out(DIR_EOME, DIR_TX(port->chan), card); /* enable interrupts */ + + sca_set_carrier(port); + netif_napi_add(port->netdev, &port->napi, sca_poll, NAPI_WEIGHT); +} + + +/* MSCI interrupt service */ +static inline void sca_msci_intr(port_t *port) +{ + u16 msci = get_msci(port); + card_t* card = port->card; + + if (sca_in(msci + ST1, card) & ST1_CDCD) { + /* Reset MSCI CDCD status bit */ + sca_out(ST1_CDCD, msci + ST1, card); + sca_set_carrier(port); + } +} + + +static inline void sca_rx(card_t *card, port_t *port, pkt_desc __iomem *desc, + u16 rxin) +{ + struct net_device *dev = port->netdev; + struct sk_buff *skb; + u16 len; + u32 buff; + + len = readw(&desc->len); + skb = dev_alloc_skb(len); + if (!skb) { + dev->stats.rx_dropped++; + return; + } + + buff = buffer_offset(port, rxin, 0); + memcpy_fromio(skb->data, card->rambase + buff, len); + + skb_put(skb, len); +#ifdef DEBUG_PKT + printk(KERN_DEBUG "%s RX(%i):", dev->name, skb->len); + debug_frame(skb); +#endif + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + skb->protocol = hdlc_type_trans(skb, dev); + netif_receive_skb(skb); +} + + +/* Receive DMA service */ +static inline int sca_rx_done(port_t *port, int budget) +{ + struct net_device *dev = port->netdev; + u16 dmac = get_dmac_rx(port); + card_t *card = port->card; + u8 stat = sca_in(DSR_RX(port->chan), card); /* read DMA Status */ + int received = 0; + + /* Reset DSR status bits */ + sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE, + DSR_RX(port->chan), card); + + if (stat & DSR_BOF) + /* Dropped one or more frames */ + dev->stats.rx_over_errors++; + + while (received < budget) { + u32 desc_off = desc_offset(port, port->rxin, 0); + pkt_desc __iomem *desc; + u32 cda = sca_inl(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) { + dev->stats.rx_errors++; + if (stat & ST_RX_OVERRUN) + dev->stats.rx_fifo_errors++; + else if ((stat & (ST_RX_SHORT | ST_RX_ABORT | + ST_RX_RESBIT)) || port->rxpart) + dev->stats.rx_frame_errors++; + else if (stat & ST_RX_CRC) + dev->stats.rx_crc_errors++; + if (stat & ST_RX_EOM) + port->rxpart = 0; /* received last fragment */ + } else { + sca_rx(card, port, desc, port->rxin); + received++; + } + + /* Set new error descriptor address */ + sca_outl(desc_off, dmac + EDAL, card); + port->rxin = (port->rxin + 1) % card->rx_ring_buffers; + } + + /* make sure RX DMA is enabled */ + sca_out(DSR_DE, DSR_RX(port->chan), card); + return received; +} + + +/* Transmit DMA service */ +static inline void sca_tx_done(port_t *port) +{ + struct net_device *dev = port->netdev; + card_t* card = port->card; + u8 stat; + unsigned count = 0; + + spin_lock(&port->lock); + + stat = sca_in(DSR_TX(port->chan), card); /* read DMA Status */ + + /* Reset DSR status bits */ + sca_out((stat & (DSR_EOT | DSR_EOM | DSR_BOF | DSR_COF)) | DSR_DWE, + DSR_TX(port->chan), card); + + while (1) { + pkt_desc __iomem *desc = desc_address(port, port->txlast, 1); + u8 stat = readb(&desc->stat); + + if (!(stat & ST_TX_OWNRSHP)) + break; /* not yet transmitted */ + if (stat & ST_TX_UNDRRUN) { + dev->stats.tx_errors++; + dev->stats.tx_fifo_errors++; + } else { + dev->stats.tx_packets++; + dev->stats.tx_bytes += readw(&desc->len); + } + writeb(0, &desc->stat); /* Free descriptor */ + count++; + port->txlast = (port->txlast + 1) % card->tx_ring_buffers; + } + + if (count) + netif_wake_queue(dev); + spin_unlock(&port->lock); +} + + +static int sca_poll(struct napi_struct *napi, int budget) +{ + port_t *port = container_of(napi, port_t, napi); + u32 isr0 = sca_inl(ISR0, port->card); + int received = 0; + + if (isr0 & (port->chan ? 0x08000000 : 0x00080000)) + sca_msci_intr(port); + + if (isr0 & (port->chan ? 0x00002000 : 0x00000020)) + sca_tx_done(port); + + if (isr0 & (port->chan ? 0x00000200 : 0x00000002)) + received = sca_rx_done(port, budget); + + if (received < budget) { + napi_complete_done(napi, received); + enable_intr(port); + } + + return received; +} + +static irqreturn_t sca_intr(int irq, void *dev_id) +{ + card_t *card = dev_id; + u32 isr0 = sca_inl(ISR0, card); + int i, handled = 0; + + for (i = 0; i < 2; i++) { + port_t *port = get_port(card, i); + if (port && (isr0 & (i ? 0x08002200 : 0x00080022))) { + handled = 1; + disable_intr(port); + napi_schedule(&port->napi); + } + } + + return IRQ_RETVAL(handled); +} + + +static void sca_set_port(port_t *port) +{ + card_t* card = port->card; + 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*/ + sca_out(port->tmc, msci + TMCR, card); + sca_out(port->tmc, msci + TMCT, card); + + /* 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->card; + 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; + case PARITY_CRC32_PR1_CCITT: md0 = MD0_HDLC | MD0_CRC_ITU32; break; + 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 */ + /* Skip the rest of underrun frame */ + sca_out(CTL_IDLE | CTL_URCT | CTL_URSKP, msci + CTL, card); + 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*/ + +/* We're using the following interrupts: + - RXINTA (DCD changes only) + - DMIB (EOM - single frame transfer complete) +*/ + sca_outl(IE0_RXINTA | IE0_CDCD, msci + IE0, card); + + sca_out(port->tmc, msci + TMCR, card); + sca_out(port->tmc, msci + TMCT, card); + 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); + + sca_set_carrier(port); + enable_intr(port); + napi_enable(&port->napi); + netif_start_queue(dev); +} + + +static void sca_close(struct net_device *dev) +{ + port_t *port = dev_to_port(dev); + + /* reset channel */ + sca_out(CMD_RESET, get_msci(port) + CMD, port->card); + disable_intr(port); + napi_disable(&port->napi); + 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 && + parity != PARITY_CRC32_PR1_CCITT && + 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->card; + u16 cnt; + + printk(KERN_DEBUG "RX ring: CDA=%u EDA=%u DSR=%02X in=%u %sactive", + sca_inl(get_dmac_rx(port) + CDAL, card), + sca_inl(get_dmac_rx(port) + EDAL, card), + sca_in(DSR_RX(port->chan), card), port->rxin, + sca_in(DSR_RX(port->chan), card) & DSR_DE ? "" : "in"); + for (cnt = 0; cnt < port->card->rx_ring_buffers; cnt++) + pr_cont(" %02X", readb(&(desc_address(port, cnt, 0)->stat))); + pr_cont("\n"); + + printk(KERN_DEBUG "TX ring: CDA=%u EDA=%u DSR=%02X in=%u " + "last=%u %sactive", + sca_inl(get_dmac_tx(port) + CDAL, card), + sca_inl(get_dmac_tx(port) + EDAL, card), + sca_in(DSR_TX(port->chan), card), port->txin, port->txlast, + sca_in(DSR_TX(port->chan), card) & DSR_DE ? "" : "in"); + + for (cnt = 0; cnt < port->card->tx_ring_buffers; cnt++) + pr_cont(" %02X", readb(&(desc_address(port, cnt, 1)->stat))); + pr_cont("\n"); + + printk(KERN_DEBUG "MSCI: MD: %02x %02x %02x," + " ST: %02x %02x %02x %02x %02x, 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), + sca_in(get_msci(port) + ST4, card), + sca_in(get_msci(port) + FST, card), + sca_in(get_msci(port) + CST0, card), + sca_in(get_msci(port) + CST1, card)); + + printk(KERN_DEBUG "ILAR: %02x ISR: %08x %08x\n", sca_in(ILAR, card), + sca_inl(ISR0, card), sca_inl(ISR1, card)); +} +#endif /* DEBUG_RINGS */ + + +static netdev_tx_t sca_xmit(struct sk_buff *skb, struct net_device *dev) +{ + port_t *port = dev_to_port(dev); + card_t *card = port->card; + pkt_desc __iomem *desc; + u32 buff, len; + + spin_lock_irq(&port->lock); + + desc = desc_address(port, port->txin + 1, 1); + BUG_ON(readb(&desc->stat)); /* previous xmit should stop queue */ + +#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; + memcpy_toio(card->rambase + buff, skb->data, len); + + writew(len, &desc->len); + writeb(ST_TX_EOM, &desc->stat); + + port->txin = (port->txin + 1) % card->tx_ring_buffers; + sca_outl(desc_offset(port, port->txin, 1), + get_dmac_tx(port) + EDAL, card); + + sca_out(DSR_DE, DSR_TX(port->chan), 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 NETDEV_TX_OK; +} + + +static u32 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; + + do { + i -= 4; + writel(i ^ 0x12345678, rambase + i); + } while (i > 0); + + for (i = 0; i < ramsize ; i += 4) { + if (readl(rambase + i) != (i ^ 0x12345678)) + break; + } + + return i; +} + + +static void 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/hd64572.h b/drivers/net/wan/hd64572.h new file mode 100644 index 000000000..22137ee66 --- /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 transmission */ + +#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/hdlc.c b/drivers/net/wan/hdlc.c new file mode 100644 index 000000000..500463044 --- /dev/null +++ b/drivers/net/wan/hdlc.c @@ -0,0 +1,401 @@ +/* + * Generic HDLC support routines for Linux + * + * Copyright (C) 1999 - 2008 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/net_namespace.h> + + +static const char* version = "HDLC support module revision 1.22"; + +#undef DEBUG_LINK + +static struct hdlc_proto *first_proto; + +static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *p, struct net_device *orig_dev) +{ + struct hdlc_device *hdlc; + + /* First make sure "dev" is an HDLC device */ + if (!(dev->priv_flags & IFF_WAN_HDLC)) { + kfree_skb(skb); + return NET_RX_SUCCESS; + } + + hdlc = dev_to_hdlc(dev); + + if (!net_eq(dev_net(dev), &init_net)) { + kfree_skb(skb); + return 0; + } + + BUG_ON(!hdlc->proto->netif_rx); + return hdlc->proto->netif_rx(skb); +} + +netdev_tx_t hdlc_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + + if (hdlc->proto->xmit) + return hdlc->proto->xmit(skb, dev); + + return hdlc->xmit(skb, dev); /* call hardware driver directly */ +} + +static inline void hdlc_proto_start(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + if (hdlc->proto->start) + hdlc->proto->start(dev); +} + + + +static inline void hdlc_proto_stop(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + if (hdlc->proto->stop) + hdlc->proto->stop(dev); +} + + + +static int hdlc_device_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + hdlc_device *hdlc; + unsigned long flags; + int on; + + if (!net_eq(dev_net(dev), &init_net)) + return NOTIFY_DONE; + + if (!(dev->priv_flags & IFF_WAN_HDLC)) + return NOTIFY_DONE; /* not an HDLC device */ + + if (event != NETDEV_CHANGE) + return NOTIFY_DONE; /* Only interested in carrier changes */ + + on = netif_carrier_ok(dev); + +#ifdef DEBUG_LINK + printk(KERN_DEBUG "%s: hdlc_device_event NETDEV_CHANGE, carrier %i\n", + dev->name, on); +#endif + + hdlc = dev_to_hdlc(dev); + spin_lock_irqsave(&hdlc->state_lock, flags); + + if (hdlc->carrier == on) + goto carrier_exit; /* no change in DCD line level */ + + hdlc->carrier = on; + + if (!hdlc->open) + goto carrier_exit; + + if (hdlc->carrier) { + netdev_info(dev, "Carrier detected\n"); + hdlc_proto_start(dev); + } else { + netdev_info(dev, "Carrier lost\n"); + hdlc_proto_stop(dev); + } + +carrier_exit: + spin_unlock_irqrestore(&hdlc->state_lock, flags); + return NOTIFY_DONE; +} + + + +/* 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 "%s: hdlc_open() carrier %i open %i\n", dev->name, + hdlc->carrier, hdlc->open); +#endif + + if (hdlc->proto == NULL) + 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) { + netdev_info(dev, "Carrier detected\n"); + hdlc_proto_start(dev); + } else + netdev_info(dev, "No carrier\n"); + + 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 "%s: hdlc_close() carrier %i open %i\n", dev->name, + hdlc->carrier, hdlc->open); +#endif + + spin_lock_irq(&hdlc->state_lock); + + hdlc->open = 0; + if (hdlc->carrier) + hdlc_proto_stop(dev); + + spin_unlock_irq(&hdlc->state_lock); + + if (hdlc->proto->close) + hdlc->proto->close(dev); +} + + + +int hdlc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct hdlc_proto *proto = first_proto; + int result; + + if (cmd != SIOCWANDEV) + return -EINVAL; + + if (dev_to_hdlc(dev)->proto) { + result = dev_to_hdlc(dev)->proto->ioctl(dev, ifr); + if (result != -EINVAL) + return result; + } + + /* Not handled by currently attached protocol (if any) */ + + while (proto) { + if ((result = proto->ioctl(dev, ifr)) != -EINVAL) + return result; + proto = proto->next; + } + return -EINVAL; +} + +static const struct header_ops hdlc_null_ops; + +static void hdlc_setup_dev(struct net_device *dev) +{ + /* Re-init all variables changed by HDLC protocol drivers, + * including ether_setup() called from hdlc_raw_eth.c. + */ + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->priv_flags = IFF_WAN_HDLC; + dev->mtu = HDLC_MAX_MTU; + dev->min_mtu = 68; + dev->max_mtu = HDLC_MAX_MTU; + dev->type = ARPHRD_RAWHDLC; + dev->hard_header_len = 16; + dev->addr_len = 0; + dev->header_ops = &hdlc_null_ops; +} + +static void hdlc_setup(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + + hdlc_setup_dev(dev); + 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(struct hdlc_device), "hdlc%d", + NET_NAME_UNKNOWN, hdlc_setup); + if (dev) + dev_to_hdlc(dev)->priv = priv; + return dev; +} + +void unregister_hdlc_device(struct net_device *dev) +{ + rtnl_lock(); + detach_hdlc_protocol(dev); + unregister_netdevice(dev); + rtnl_unlock(); +} + + + +int attach_hdlc_protocol(struct net_device *dev, struct hdlc_proto *proto, + size_t size) +{ + int err; + + err = detach_hdlc_protocol(dev); + if (err) + return err; + + if (!try_module_get(proto->module)) + return -ENOSYS; + + if (size) { + dev_to_hdlc(dev)->state = kmalloc(size, GFP_KERNEL); + if (dev_to_hdlc(dev)->state == NULL) { + module_put(proto->module); + return -ENOBUFS; + } + } + dev_to_hdlc(dev)->proto = proto; + + return 0; +} + + +int detach_hdlc_protocol(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + int err; + + if (hdlc->proto) { + err = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE, dev); + err = notifier_to_errno(err); + if (err) { + netdev_err(dev, "Refused to change device type\n"); + return err; + } + + if (hdlc->proto->detach) + hdlc->proto->detach(dev); + module_put(hdlc->proto->module); + hdlc->proto = NULL; + } + kfree(hdlc->state); + hdlc->state = NULL; + hdlc_setup_dev(dev); + + return 0; +} + + +void register_hdlc_protocol(struct hdlc_proto *proto) +{ + rtnl_lock(); + proto->next = first_proto; + first_proto = proto; + rtnl_unlock(); +} + + +void unregister_hdlc_protocol(struct hdlc_proto *proto) +{ + struct hdlc_proto **p; + + rtnl_lock(); + p = &first_proto; + while (*p != proto) { + BUG_ON(!*p); + p = &((*p)->next); + } + *p = proto->next; + rtnl_unlock(); +} + + + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("HDLC support module"); +MODULE_LICENSE("GPL v2"); + +EXPORT_SYMBOL(hdlc_start_xmit); +EXPORT_SYMBOL(hdlc_open); +EXPORT_SYMBOL(hdlc_close); +EXPORT_SYMBOL(hdlc_ioctl); +EXPORT_SYMBOL(alloc_hdlcdev); +EXPORT_SYMBOL(unregister_hdlc_device); +EXPORT_SYMBOL(register_hdlc_protocol); +EXPORT_SYMBOL(unregister_hdlc_protocol); +EXPORT_SYMBOL(attach_hdlc_protocol); +EXPORT_SYMBOL(detach_hdlc_protocol); + +static struct packet_type hdlc_packet_type __read_mostly = { + .type = cpu_to_be16(ETH_P_HDLC), + .func = hdlc_rcv, +}; + + +static struct notifier_block hdlc_notifier = { + .notifier_call = hdlc_device_event, +}; + + +static int __init hdlc_module_init(void) +{ + int result; + + pr_info("%s\n", version); + if ((result = register_netdevice_notifier(&hdlc_notifier)) != 0) + return result; + dev_add_pack(&hdlc_packet_type); + return 0; +} + + + +static void __exit hdlc_module_exit(void) +{ + dev_remove_pack(&hdlc_packet_type); + unregister_netdevice_notifier(&hdlc_notifier); +} + + +module_init(hdlc_module_init); +module_exit(hdlc_module_exit); diff --git a/drivers/net/wan/hdlc_cisco.c b/drivers/net/wan/hdlc_cisco.c new file mode 100644 index 000000000..2c6e3fa69 --- /dev/null +++ b/drivers/net/wan/hdlc_cisco.c @@ -0,0 +1,408 @@ +/* + * Generic HDLC support routines for Linux + * Cisco HDLC support + * + * Copyright (C) 2000 - 2006 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/errno.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.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 */ + + +struct hdlc_header { + u8 address; + u8 control; + __be16 protocol; +}__packed; + + +struct cisco_packet { + __be32 type; /* code */ + __be32 par1; + __be32 par2; + __be16 rel; /* reliability */ + __be32 time; +}__packed; +#define CISCO_PACKET_LEN 18 +#define CISCO_BIG_PACKET_LEN 20 + + +struct cisco_state { + cisco_proto settings; + + struct timer_list timer; + struct net_device *dev; + spinlock_t lock; + unsigned long last_poll; + int up; + u32 txseq; /* TX sequence number, 0 = none */ + u32 rxseq; /* RX sequence number */ +}; + + +static int cisco_ioctl(struct net_device *dev, struct ifreq *ifr); + + +static inline struct cisco_state* state(hdlc_device *hdlc) +{ + return (struct cisco_state *)hdlc->state; +} + + +static int cisco_hard_header(struct sk_buff *skb, struct net_device *dev, + u16 type, const void *daddr, const void *saddr, + unsigned int len) +{ + struct hdlc_header *data; +#ifdef DEBUG_HARD_HEADER + printk(KERN_DEBUG "%s: cisco_hard_header called\n", dev->name); +#endif + + skb_push(skb, sizeof(struct hdlc_header)); + data = (struct 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(struct hdlc_header); +} + + + +static void cisco_keepalive_send(struct net_device *dev, u32 type, + __be32 par1, __be32 par2) +{ + struct sk_buff *skb; + struct cisco_packet *data; + + skb = dev_alloc_skb(sizeof(struct hdlc_header) + + sizeof(struct cisco_packet)); + if (!skb) { + netdev_warn(dev, "Memory squeeze on cisco_keepalive_send()\n"); + return; + } + skb_reserve(skb, 4); + cisco_hard_header(skb, dev, CISCO_KEEPALIVE, NULL, NULL, 0); + data = (struct cisco_packet*)(skb->data + 4); + + data->type = htonl(type); + data->par1 = par1; + data->par2 = par2; + data->rel = cpu_to_be16(0xFFFF); + /* we will need do_div here if 1000 % HZ != 0 */ + data->time = htonl((jiffies - INITIAL_JIFFIES) * (1000 / HZ)); + + skb_put(skb, sizeof(struct cisco_packet)); + skb->priority = TC_PRIO_CONTROL; + skb->dev = dev; + skb->protocol = htons(ETH_P_HDLC); + skb_reset_network_header(skb); + + dev_queue_xmit(skb); +} + + + +static __be16 cisco_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + struct hdlc_header *data = (struct hdlc_header*)skb->data; + + if (skb->len < sizeof(struct hdlc_header)) + return cpu_to_be16(ETH_P_HDLC); + + if (data->address != CISCO_MULTICAST && + data->address != CISCO_UNICAST) + return cpu_to_be16(ETH_P_HDLC); + + switch (data->protocol) { + case cpu_to_be16(ETH_P_IP): + case cpu_to_be16(ETH_P_IPX): + case cpu_to_be16(ETH_P_IPV6): + skb_pull(skb, sizeof(struct hdlc_header)); + return data->protocol; + default: + return cpu_to_be16(ETH_P_HDLC); + } +} + + +static int cisco_rx(struct sk_buff *skb) +{ + struct net_device *dev = skb->dev; + hdlc_device *hdlc = dev_to_hdlc(dev); + struct cisco_state *st = state(hdlc); + struct hdlc_header *data = (struct hdlc_header*)skb->data; + struct cisco_packet *cisco_data; + struct in_device *in_dev; + __be32 addr, mask; + u32 ack; + + if (skb->len < sizeof(struct 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(struct hdlc_header) + + CISCO_PACKET_LEN) && + (skb->len != sizeof(struct hdlc_header) + + CISCO_BIG_PACKET_LEN)) { + netdev_info(dev, "Invalid length of Cisco control packet (%d bytes)\n", + skb->len); + goto rx_error; + } + + cisco_data = (struct cisco_packet*)(skb->data + sizeof + (struct hdlc_header)); + + switch (ntohl (cisco_data->type)) { + case CISCO_ADDR_REQ: /* Stolen from syncppp.c :-) */ + rcu_read_lock(); + in_dev = __in_dev_get_rcu(dev); + addr = 0; + mask = ~cpu_to_be32(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); + } + rcu_read_unlock(); + dev_kfree_skb_any(skb); + return NET_RX_SUCCESS; + + case CISCO_ADDR_REPLY: + netdev_info(dev, "Unexpected Cisco IP address reply\n"); + goto rx_error; + + case CISCO_KEEPALIVE_REQ: + spin_lock(&st->lock); + st->rxseq = ntohl(cisco_data->par1); + ack = ntohl(cisco_data->par2); + if (ack && (ack == st->txseq || + /* our current REQ may be in transit */ + ack == st->txseq - 1)) { + st->last_poll = jiffies; + if (!st->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; + netdev_info(dev, "Link up (peer uptime %ud%uh%um%us)\n", + days, hrs, min, sec); + netif_dormant_off(dev); + st->up = 1; + } + } + spin_unlock(&st->lock); + + dev_kfree_skb_any(skb); + return NET_RX_SUCCESS; + } /* switch (keepalive type) */ + } /* switch (protocol) */ + + netdev_info(dev, "Unsupported protocol %x\n", ntohs(data->protocol)); + dev_kfree_skb_any(skb); + return NET_RX_DROP; + +rx_error: + dev->stats.rx_errors++; /* Mark error */ + dev_kfree_skb_any(skb); + return NET_RX_DROP; +} + + + +static void cisco_timer(struct timer_list *t) +{ + struct cisco_state *st = from_timer(st, t, timer); + struct net_device *dev = st->dev; + + spin_lock(&st->lock); + if (st->up && + time_after(jiffies, st->last_poll + st->settings.timeout * HZ)) { + st->up = 0; + netdev_info(dev, "Link down\n"); + netif_dormant_on(dev); + } + + cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ, htonl(++st->txseq), + htonl(st->rxseq)); + spin_unlock(&st->lock); + + st->timer.expires = jiffies + st->settings.interval * HZ; + add_timer(&st->timer); +} + + + +static void cisco_start(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct cisco_state *st = state(hdlc); + unsigned long flags; + + spin_lock_irqsave(&st->lock, flags); + st->up = st->txseq = st->rxseq = 0; + spin_unlock_irqrestore(&st->lock, flags); + + st->dev = dev; + timer_setup(&st->timer, cisco_timer, 0); + st->timer.expires = jiffies + HZ; /* First poll after 1 s */ + add_timer(&st->timer); +} + + + +static void cisco_stop(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct cisco_state *st = state(hdlc); + unsigned long flags; + + del_timer_sync(&st->timer); + + spin_lock_irqsave(&st->lock, flags); + netif_dormant_on(dev); + st->up = st->txseq = 0; + spin_unlock_irqrestore(&st->lock, flags); +} + + +static struct hdlc_proto proto = { + .start = cisco_start, + .stop = cisco_stop, + .type_trans = cisco_type_trans, + .ioctl = cisco_ioctl, + .netif_rx = cisco_rx, + .module = THIS_MODULE, +}; + +static const struct header_ops cisco_header_ops = { + .create = cisco_hard_header, +}; + +static int 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: + if (dev_to_hdlc(dev)->proto != &proto) + return -EINVAL; + 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, &state(hdlc)->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; + + result = attach_hdlc_protocol(dev, &proto, + sizeof(struct cisco_state)); + if (result) + return result; + + memcpy(&state(hdlc)->settings, &new_settings, size); + spin_lock_init(&state(hdlc)->lock); + dev->header_ops = &cisco_header_ops; + dev->hard_header_len = sizeof(struct hdlc_header); + dev->type = ARPHRD_CISCO; + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); + netif_dormant_on(dev); + return 0; + } + + return -EINVAL; +} + + +static int __init mod_init(void) +{ + register_hdlc_protocol(&proto); + return 0; +} + + + +static void __exit mod_exit(void) +{ + unregister_hdlc_protocol(&proto); +} + + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("Cisco HDLC protocol support for generic HDLC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wan/hdlc_fr.c b/drivers/net/wan/hdlc_fr.c new file mode 100644 index 000000000..96b4ce13f --- /dev/null +++ b/drivers/net/wan/hdlc_fr.c @@ -0,0 +1,1306 @@ +/* + * Generic HDLC support routines for Linux + * Frame Relay support + * + * Copyright (C) 1999 - 2006 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 + + CCITT LMI: ITU-T Q.933 Annex A + ANSI LMI: ANSI T1.617 Annex D + CISCO LMI: the original, aka "Gang of Four" LMI + +*/ + +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.h> +#include <linux/slab.h> + +#undef DEBUG_PKT +#undef DEBUG_ECN +#undef DEBUG_LINK +#undef DEBUG_PROTO +#undef DEBUG_PVC + +#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_CCITT_ANSI_LMI 0x08 +#define NLPID_CISCO_LMI 0x09 + + +#define LMI_CCITT_ANSI_DLCI 0 /* LMI DLCI */ +#define LMI_CISCO_DLCI 1023 + +#define LMI_CALLREF 0x00 /* Call Reference */ +#define LMI_ANSI_LOCKSHIFT 0x95 /* ANSI locking shift */ +#define LMI_ANSI_CISCO_REPTYPE 0x01 /* report type */ +#define LMI_CCITT_REPTYPE 0x51 +#define LMI_ANSI_CISCO_ALIVE 0x03 /* keep alive */ +#define LMI_CCITT_ALIVE 0x53 +#define LMI_ANSI_CISCO_PVCSTAT 0x07 /* PVC status */ +#define LMI_CCITT_PVCSTAT 0x57 + +#define LMI_FULLREP 0x00 /* full report */ +#define LMI_INTEGRITY 0x01 /* link integrity report */ +#define LMI_SINGLE 0x02 /* 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_CCITT_CISCO_LENGTH 13 /* LMI frame lengths */ +#define LMI_ANSI_LENGTH 14 + + +struct fr_hdr { +#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 +} __packed; + + +struct pvc_device { + struct net_device *frad; + struct net_device *main; + struct net_device *ether; /* bridged Ethernet interface */ + struct pvc_device *next; /* Sorted in ascending DLCI order */ + int dlci; + int open_count; + + struct { + unsigned int new: 1; + unsigned int active: 1; + unsigned int exist: 1; + unsigned int deleted: 1; + unsigned int fecn: 1; + unsigned int becn: 1; + unsigned int bandwidth; /* Cisco LMI reporting only */ + }state; +}; + +struct frad_state { + fr_proto settings; + struct pvc_device *first_pvc; + int dce_pvc_count; + + struct timer_list timer; + struct net_device *dev; + unsigned long last_poll; + int reliable; + int dce_changed; + int request; + int fullrep_sent; + u32 last_errors; /* last errors bit list */ + u8 n391cnt; + u8 txseq; /* TX sequence number */ + u8 rxseq; /* RX sequence number */ +}; + + +static int fr_ioctl(struct net_device *dev, struct ifreq *ifr); + + +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 struct frad_state* state(hdlc_device *hdlc) +{ + return(struct frad_state *)(hdlc->state); +} + + +static inline struct pvc_device *find_pvc(hdlc_device *hdlc, u16 dlci) +{ + struct pvc_device *pvc = state(hdlc)->first_pvc; + + while (pvc) { + if (pvc->dlci == dlci) + return pvc; + if (pvc->dlci > dlci) + return NULL; /* the list is sorted */ + pvc = pvc->next; + } + + return NULL; +} + + +static struct pvc_device *add_pvc(struct net_device *dev, u16 dlci) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct pvc_device *pvc, **pvc_p = &state(hdlc)->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 = kzalloc(sizeof(*pvc), GFP_ATOMIC); +#ifdef DEBUG_PVC + printk(KERN_DEBUG "add_pvc: allocated pvc %p, frad %p\n", pvc, dev); +#endif + if (!pvc) + return NULL; + + pvc->dlci = dlci; + pvc->frad = dev; + pvc->next = *pvc_p; /* Put it in the chain */ + *pvc_p = pvc; + return pvc; +} + + +static inline int pvc_is_used(struct pvc_device *pvc) +{ + return pvc->main || pvc->ether; +} + + +static inline void pvc_carrier(int on, struct 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) +{ + struct pvc_device **pvc_p = &state(hdlc)->first_pvc; + + while (*pvc_p) { + if (!pvc_is_used(*pvc_p)) { + struct pvc_device *pvc = *pvc_p; +#ifdef DEBUG_PVC + printk(KERN_DEBUG "freeing unused pvc: %p\n", pvc); +#endif + *pvc_p = pvc->next; + kfree(pvc); + continue; + } + pvc_p = &(*pvc_p)->next; + } +} + + +static inline struct net_device **get_dev_p(struct pvc_device *pvc, + int type) +{ + if (type == ARPHRD_ETHER) + return &pvc->ether; + else + return &pvc->main; +} + + +static int fr_hard_header(struct sk_buff **skb_p, u16 dlci) +{ + struct sk_buff *skb = *skb_p; + + if (!skb->dev) { /* Control packets */ + switch (dlci) { + case LMI_CCITT_ANSI_DLCI: + skb_push(skb, 4); + skb->data[3] = NLPID_CCITT_ANSI_LMI; + break; + + case LMI_CISCO_DLCI: + skb_push(skb, 4); + skb->data[3] = NLPID_CISCO_LMI; + break; + + default: + return -EINVAL; + } + + } else if (skb->dev->type == ARPHRD_DLCI) { + switch (skb->protocol) { + case htons(ETH_P_IP): + skb_push(skb, 4); + skb->data[3] = NLPID_IP; + break; + + case htons(ETH_P_IPV6): + skb_push(skb, 4); + skb->data[3] = NLPID_IPV6; + break; + + default: + skb_push(skb, 10); + skb->data[3] = FR_PAD; + skb->data[4] = NLPID_SNAP; + /* OUI 00-00-00 indicates an Ethertype follows */ + skb->data[5] = 0x00; + skb->data[6] = 0x00; + skb->data[7] = 0x00; + /* This should be an Ethertype: */ + *(__be16 *)(skb->data + 8) = skb->protocol; + } + + } else if (skb->dev->type == ARPHRD_ETHER) { + if (skb_headroom(skb) < 10) { + struct sk_buff *skb2 = skb_realloc_headroom(skb, 10); + if (!skb2) + return -ENOBUFS; + dev_kfree_skb(skb); + skb = *skb_p = skb2; + } + skb_push(skb, 10); + skb->data[3] = FR_PAD; + skb->data[4] = NLPID_SNAP; + /* OUI 00-80-C2 stands for the 802.1 organization */ + skb->data[5] = 0x00; + skb->data[6] = 0x80; + skb->data[7] = 0xC2; + /* PID 00-07 stands for Ethernet frames without FCS */ + skb->data[8] = 0x00; + skb->data[9] = 0x07; + + } else { + return -EINVAL; + } + + dlci_to_q922(skb->data, dlci); + skb->data[2] = FR_UI; + return 0; +} + + + +static int pvc_open(struct net_device *dev) +{ + struct pvc_device *pvc = dev->ml_priv; + + if ((pvc->frad->flags & IFF_UP) == 0) + return -EIO; /* Frad must be UP in order to activate PVC */ + + if (pvc->open_count++ == 0) { + hdlc_device *hdlc = dev_to_hdlc(pvc->frad); + if (state(hdlc)->settings.lmi == LMI_NONE) + pvc->state.active = netif_carrier_ok(pvc->frad); + + pvc_carrier(pvc->state.active, pvc); + state(hdlc)->dce_changed = 1; + } + return 0; +} + + + +static int pvc_close(struct net_device *dev) +{ + struct pvc_device *pvc = dev->ml_priv; + + if (--pvc->open_count == 0) { + hdlc_device *hdlc = dev_to_hdlc(pvc->frad); + if (state(hdlc)->settings.lmi == LMI_NONE) + pvc->state.active = 0; + + if (state(hdlc)->settings.dce) { + state(hdlc)->dce_changed = 1; + pvc->state.active = 0; + } + } + return 0; +} + + + +static int pvc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct pvc_device *pvc = dev->ml_priv; + 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->frad->name, IFNAMSIZ); + if (copy_to_user(ifr->ifr_settings.ifs_ifsu.fr_pvc_info, + &info, sizeof(info))) + return -EFAULT; + return 0; + } + + return -EINVAL; +} + +static netdev_tx_t pvc_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct pvc_device *pvc = dev->ml_priv; + + 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)) { + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + skb_put(skb, pad); + memset(skb->data + len, 0, pad); + } + } + skb->dev = dev; + if (!fr_hard_header(&skb, pvc->dlci)) { + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + if (pvc->state.fecn) /* TX Congestion counter */ + dev->stats.tx_compressed++; + skb->dev = pvc->frad; + skb->protocol = htons(ETH_P_HDLC); + skb_reset_network_header(skb); + dev_queue_xmit(skb); + return NETDEV_TX_OK; + } + } + + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static inline void fr_log_dlci_active(struct pvc_device *pvc) +{ + netdev_info(pvc->frad, "DLCI %d [%s%s%s]%s %s\n", + 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; + struct pvc_device *pvc = state(hdlc)->first_pvc; + int lmi = state(hdlc)->settings.lmi; + int dce = state(hdlc)->settings.dce; + int len = lmi == LMI_ANSI ? LMI_ANSI_LENGTH : LMI_CCITT_CISCO_LENGTH; + int stat_len = (lmi == LMI_CISCO) ? 6 : 3; + u8 *data; + int i = 0; + + if (dce && fullrep) { + len += state(hdlc)->dce_pvc_count * (2 + stat_len); + if (len > HDLC_MAX_MRU) { + netdev_warn(dev, "Too many PVCs while sending LMI full report\n"); + return; + } + } + + skb = dev_alloc_skb(len); + if (!skb) { + netdev_warn(dev, "Memory squeeze on fr_lmi_send()\n"); + return; + } + memset(skb->data, 0, len); + skb_reserve(skb, 4); + if (lmi == LMI_CISCO) { + fr_hard_header(&skb, LMI_CISCO_DLCI); + } else { + fr_hard_header(&skb, LMI_CCITT_ANSI_DLCI); + } + data = skb_tail_pointer(skb); + data[i++] = LMI_CALLREF; + data[i++] = dce ? LMI_STATUS : LMI_STATUS_ENQUIRY; + if (lmi == LMI_ANSI) + data[i++] = LMI_ANSI_LOCKSHIFT; + data[i++] = lmi == LMI_CCITT ? LMI_CCITT_REPTYPE : + LMI_ANSI_CISCO_REPTYPE; + data[i++] = LMI_REPT_LEN; + data[i++] = fullrep ? LMI_FULLREP : LMI_INTEGRITY; + data[i++] = lmi == LMI_CCITT ? LMI_CCITT_ALIVE : LMI_ANSI_CISCO_ALIVE; + data[i++] = LMI_INTEG_LEN; + data[i++] = state(hdlc)->txseq = + fr_lmi_nextseq(state(hdlc)->txseq); + data[i++] = state(hdlc)->rxseq; + + if (dce && fullrep) { + while (pvc) { + data[i++] = lmi == LMI_CCITT ? LMI_CCITT_PVCSTAT : + LMI_ANSI_CISCO_PVCSTAT; + data[i++] = stat_len; + + /* LMI start/restart */ + if (state(hdlc)->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); + } + + if (lmi == LMI_CISCO) { + data[i] = pvc->dlci >> 8; + data[i + 1] = pvc->dlci & 0xFF; + } else { + data[i] = (pvc->dlci >> 4) & 0x3F; + data[i + 1] = ((pvc->dlci << 3) & 0x78) | 0x80; + data[i + 2] = 0x80; + } + + if (pvc->state.new) + data[i + 2] |= 0x08; + else if (pvc->state.active) + data[i + 2] |= 0x02; + + i += stat_len; + pvc = pvc->next; + } + } + + skb_put(skb, i); + skb->priority = TC_PRIO_CONTROL; + skb->dev = dev; + skb->protocol = htons(ETH_P_HDLC); + skb_reset_network_header(skb); + + dev_queue_xmit(skb); +} + + + +static void fr_set_link_state(int reliable, struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct pvc_device *pvc = state(hdlc)->first_pvc; + + state(hdlc)->reliable = reliable; + if (reliable) { + netif_dormant_off(dev); + state(hdlc)->n391cnt = 0; /* Request full status */ + state(hdlc)->dce_changed = 1; + + if (state(hdlc)->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 { + netif_dormant_on(dev); + while (pvc) { /* Deactivate all PVCs */ + pvc_carrier(0, pvc); + pvc->state.exist = pvc->state.active = 0; + pvc->state.new = 0; + if (!state(hdlc)->settings.dce) + pvc->state.bandwidth = 0; + pvc = pvc->next; + } + } +} + + +static void fr_timer(struct timer_list *t) +{ + struct frad_state *st = from_timer(st, t, timer); + struct net_device *dev = st->dev; + hdlc_device *hdlc = dev_to_hdlc(dev); + int i, cnt = 0, reliable; + u32 list; + + if (state(hdlc)->settings.dce) { + reliable = state(hdlc)->request && + time_before(jiffies, state(hdlc)->last_poll + + state(hdlc)->settings.t392 * HZ); + state(hdlc)->request = 0; + } else { + state(hdlc)->last_errors <<= 1; /* Shift the list */ + if (state(hdlc)->request) { + if (state(hdlc)->reliable) + netdev_info(dev, "No LMI status reply received\n"); + state(hdlc)->last_errors |= 1; + } + + list = state(hdlc)->last_errors; + for (i = 0; i < state(hdlc)->settings.n393; i++, list >>= 1) + cnt += (list & 1); /* errors count */ + + reliable = (cnt < state(hdlc)->settings.n392); + } + + if (state(hdlc)->reliable != reliable) { + netdev_info(dev, "Link %sreliable\n", reliable ? "" : "un"); + fr_set_link_state(reliable, dev); + } + + if (state(hdlc)->settings.dce) + state(hdlc)->timer.expires = jiffies + + state(hdlc)->settings.t392 * HZ; + else { + if (state(hdlc)->n391cnt) + state(hdlc)->n391cnt--; + + fr_lmi_send(dev, state(hdlc)->n391cnt == 0); + + state(hdlc)->last_poll = jiffies; + state(hdlc)->request = 1; + state(hdlc)->timer.expires = jiffies + + state(hdlc)->settings.t391 * HZ; + } + + add_timer(&state(hdlc)->timer); +} + + +static int fr_lmi_recv(struct net_device *dev, struct sk_buff *skb) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct pvc_device *pvc; + u8 rxseq, txseq; + int lmi = state(hdlc)->settings.lmi; + int dce = state(hdlc)->settings.dce; + int stat_len = (lmi == LMI_CISCO) ? 6 : 3, reptype, error, no_ram, i; + + if (skb->len < (lmi == LMI_ANSI ? LMI_ANSI_LENGTH : + LMI_CCITT_CISCO_LENGTH)) { + netdev_info(dev, "Short LMI frame\n"); + return 1; + } + + if (skb->data[3] != (lmi == LMI_CISCO ? NLPID_CISCO_LMI : + NLPID_CCITT_ANSI_LMI)) { + netdev_info(dev, "Received non-LMI frame with LMI DLCI\n"); + return 1; + } + + if (skb->data[4] != LMI_CALLREF) { + netdev_info(dev, "Invalid LMI Call reference (0x%02X)\n", + skb->data[4]); + return 1; + } + + if (skb->data[5] != (dce ? LMI_STATUS_ENQUIRY : LMI_STATUS)) { + netdev_info(dev, "Invalid LMI Message type (0x%02X)\n", + skb->data[5]); + return 1; + } + + if (lmi == LMI_ANSI) { + if (skb->data[6] != LMI_ANSI_LOCKSHIFT) { + netdev_info(dev, "Not ANSI locking shift in LMI message (0x%02X)\n", + skb->data[6]); + return 1; + } + i = 7; + } else + i = 6; + + if (skb->data[i] != (lmi == LMI_CCITT ? LMI_CCITT_REPTYPE : + LMI_ANSI_CISCO_REPTYPE)) { + netdev_info(dev, "Not an LMI Report type IE (0x%02X)\n", + skb->data[i]); + return 1; + } + + if (skb->data[++i] != LMI_REPT_LEN) { + netdev_info(dev, "Invalid LMI Report type IE length (%u)\n", + skb->data[i]); + return 1; + } + + reptype = skb->data[++i]; + if (reptype != LMI_INTEGRITY && reptype != LMI_FULLREP) { + netdev_info(dev, "Unsupported LMI Report type (0x%02X)\n", + reptype); + return 1; + } + + if (skb->data[++i] != (lmi == LMI_CCITT ? LMI_CCITT_ALIVE : + LMI_ANSI_CISCO_ALIVE)) { + netdev_info(dev, "Not an LMI Link integrity verification IE (0x%02X)\n", + skb->data[i]); + return 1; + } + + if (skb->data[++i] != LMI_INTEG_LEN) { + netdev_info(dev, "Invalid LMI Link integrity verification IE length (%u)\n", + skb->data[i]); + return 1; + } + i++; + + state(hdlc)->rxseq = skb->data[i++]; /* TX sequence from peer */ + rxseq = skb->data[i++]; /* Should confirm our sequence */ + + txseq = state(hdlc)->txseq; + + if (dce) + state(hdlc)->last_poll = jiffies; + + error = 0; + if (!state(hdlc)->reliable) + error = 1; + + if (rxseq == 0 || rxseq != txseq) { /* Ask for full report next time */ + state(hdlc)->n391cnt = 0; + error = 1; + } + + if (dce) { + if (state(hdlc)->fullrep_sent && !error) { +/* Stop sending full report - the last one has been confirmed by DTE */ + state(hdlc)->fullrep_sent = 0; + pvc = state(hdlc)->first_pvc; + while (pvc) { + if (pvc->state.new) { + pvc->state.new = 0; + +/* Tell DTE that new PVC is now active */ + state(hdlc)->dce_changed = 1; + } + pvc = pvc->next; + } + } + + if (state(hdlc)->dce_changed) { + reptype = LMI_FULLREP; + state(hdlc)->fullrep_sent = 1; + state(hdlc)->dce_changed = 0; + } + + state(hdlc)->request = 1; /* got request */ + fr_lmi_send(dev, reptype == LMI_FULLREP ? 1 : 0); + return 0; + } + + /* DTE */ + + state(hdlc)->request = 0; /* got response, no request pending */ + + if (error) + return 0; + + if (reptype != LMI_FULLREP) + return 0; + + pvc = state(hdlc)->first_pvc; + + while (pvc) { + pvc->state.deleted = 1; + pvc = pvc->next; + } + + no_ram = 0; + while (skb->len >= i + 2 + stat_len) { + u16 dlci; + u32 bw; + unsigned int active, new; + + if (skb->data[i] != (lmi == LMI_CCITT ? LMI_CCITT_PVCSTAT : + LMI_ANSI_CISCO_PVCSTAT)) { + netdev_info(dev, "Not an LMI PVC status IE (0x%02X)\n", + skb->data[i]); + return 1; + } + + if (skb->data[++i] != stat_len) { + netdev_info(dev, "Invalid LMI PVC status IE length (%u)\n", + skb->data[i]); + return 1; + } + i++; + + new = !! (skb->data[i + 2] & 0x08); + active = !! (skb->data[i + 2] & 0x02); + if (lmi == LMI_CISCO) { + dlci = (skb->data[i] << 8) | skb->data[i + 1]; + bw = (skb->data[i + 3] << 16) | + (skb->data[i + 4] << 8) | + (skb->data[i + 5]); + } else { + dlci = ((skb->data[i] & 0x3F) << 4) | + ((skb->data[i + 1] & 0x78) >> 3); + bw = 0; + } + + pvc = add_pvc(dev, dlci); + + if (!pvc && !no_ram) { + netdev_warn(dev, "Memory squeeze on fr_lmi_recv()\n"); + no_ram = 1; + } + + if (pvc) { + pvc->state.exist = 1; + pvc->state.deleted = 0; + if (active != pvc->state.active || + new != pvc->state.new || + bw != pvc->state.bandwidth || + !pvc->state.exist) { + pvc->state.new = new; + pvc->state.active = active; + pvc->state.bandwidth = bw; + pvc_carrier(active, pvc); + fr_log_dlci_active(pvc); + } + } + + i += stat_len; + } + + pvc = state(hdlc)->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; + pvc->state.bandwidth = 0; + fr_log_dlci_active(pvc); + } + pvc = pvc->next; + } + + /* Next full report after N391 polls */ + state(hdlc)->n391cnt = state(hdlc)->settings.n391; + + return 0; +} + + +static int fr_rx(struct sk_buff *skb) +{ + struct net_device *frad = skb->dev; + hdlc_device *hdlc = dev_to_hdlc(frad); + struct fr_hdr *fh = (struct fr_hdr *)skb->data; + u8 *data = skb->data; + u16 dlci; + struct 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_CCITT_ANSI_DLCI && + (state(hdlc)->settings.lmi == LMI_ANSI || + state(hdlc)->settings.lmi == LMI_CCITT)) || + (dlci == LMI_CISCO_DLCI && + state(hdlc)->settings.lmi == LMI_CISCO)) { + if (fr_lmi_recv(frad, skb)) + goto rx_error; + dev_kfree_skb_any(skb); + return NET_RX_SUCCESS; + } + + pvc = find_pvc(hdlc, dlci); + if (!pvc) { +#ifdef DEBUG_PKT + netdev_info(frad, "No PVC for received frame's DLCI %d\n", + 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", frad->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", frad->name, + dlci, fh->becn ? "N" : "FF"); +#endif + pvc->state.becn ^= 1; + } + + + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { + frad->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(*(__be16*)(data + 6)); + u16 pid = ntohs(*(__be16*)(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: + netdev_info(frad, "Unsupported protocol, OUI=%x PID=%x\n", + oui, pid); + dev_kfree_skb_any(skb); + return NET_RX_DROP; + } + } else { + netdev_info(frad, "Unsupported protocol, NLPID=%x length=%i\n", + data[3], skb->len); + dev_kfree_skb_any(skb); + return NET_RX_DROP; + } + + if (dev) { + dev->stats.rx_packets++; /* PVC traffic */ + dev->stats.rx_bytes += skb->len; + if (pvc->state.becn) + dev->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: + frad->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 (state(hdlc)->settings.lmi != LMI_NONE) { + state(hdlc)->reliable = 0; + state(hdlc)->dce_changed = 1; + state(hdlc)->request = 0; + state(hdlc)->fullrep_sent = 0; + state(hdlc)->last_errors = 0xFFFFFFFF; + state(hdlc)->n391cnt = 0; + state(hdlc)->txseq = state(hdlc)->rxseq = 0; + + state(hdlc)->dev = dev; + timer_setup(&state(hdlc)->timer, fr_timer, 0); + /* First poll after 1 s */ + state(hdlc)->timer.expires = jiffies + HZ; + add_timer(&state(hdlc)->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 (state(hdlc)->settings.lmi != LMI_NONE) + del_timer_sync(&state(hdlc)->timer); + fr_set_link_state(0, dev); +} + + +static void fr_close(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct pvc_device *pvc = state(hdlc)->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 pvc_setup(struct net_device *dev) +{ + dev->type = ARPHRD_DLCI; + dev->flags = IFF_POINTOPOINT; + dev->hard_header_len = 0; + dev->addr_len = 2; + netif_keep_dst(dev); +} + +static const struct net_device_ops pvc_ops = { + .ndo_open = pvc_open, + .ndo_stop = pvc_close, + .ndo_start_xmit = pvc_xmit, + .ndo_do_ioctl = pvc_ioctl, +}; + +static int fr_add_pvc(struct net_device *frad, unsigned int dlci, int type) +{ + hdlc_device *hdlc = dev_to_hdlc(frad); + struct pvc_device *pvc; + struct net_device *dev; + int used; + + if ((pvc = add_pvc(frad, dlci)) == NULL) { + netdev_warn(frad, "Memory squeeze on fr_add_pvc()\n"); + return -ENOBUFS; + } + + if (*get_dev_p(pvc, type)) + return -EEXIST; + + used = pvc_is_used(pvc); + + if (type == ARPHRD_ETHER) + dev = alloc_netdev(0, "pvceth%d", NET_NAME_UNKNOWN, + ether_setup); + else + dev = alloc_netdev(0, "pvc%d", NET_NAME_UNKNOWN, pvc_setup); + + if (!dev) { + netdev_warn(frad, "Memory squeeze on fr_pvc()\n"); + delete_unused_pvcs(hdlc); + return -ENOBUFS; + } + + if (type == ARPHRD_ETHER) { + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + eth_hw_addr_random(dev); + } else { + *(__be16*)dev->dev_addr = htons(dlci); + dlci_to_q922(dev->broadcast, dlci); + } + dev->netdev_ops = &pvc_ops; + dev->mtu = HDLC_MAX_MTU; + dev->min_mtu = 68; + dev->max_mtu = HDLC_MAX_MTU; + dev->needed_headroom = 10; + dev->priv_flags |= IFF_NO_QUEUE; + dev->ml_priv = pvc; + + if (register_netdevice(dev) != 0) { + free_netdev(dev); + delete_unused_pvcs(hdlc); + return -EIO; + } + + dev->needs_free_netdev = true; + *get_dev_p(pvc, type) = dev; + if (!used) { + state(hdlc)->dce_changed = 1; + state(hdlc)->dce_pvc_count++; + } + return 0; +} + + + +static int fr_del_pvc(hdlc_device *hdlc, unsigned int dlci, int type) +{ + struct 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)) { + state(hdlc)->dce_pvc_count--; + state(hdlc)->dce_changed = 1; + } + delete_unused_pvcs(hdlc); + return 0; +} + + + +static void fr_destroy(struct net_device *frad) +{ + hdlc_device *hdlc = dev_to_hdlc(frad); + struct pvc_device *pvc = state(hdlc)->first_pvc; + state(hdlc)->first_pvc = NULL; /* All PVCs destroyed */ + state(hdlc)->dce_pvc_count = 0; + state(hdlc)->dce_changed = 1; + + while (pvc) { + struct 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; + } +} + + +static struct hdlc_proto proto = { + .close = fr_close, + .start = fr_start, + .stop = fr_stop, + .detach = fr_destroy, + .ioctl = fr_ioctl, + .netif_rx = fr_rx, + .module = THIS_MODULE, +}; + + +static int 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: + if (dev_to_hdlc(dev)->proto != &proto) /* Different proto */ + return -EINVAL; + 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, &state(hdlc)->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.lmi != LMI_CISCO) || + 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 (dev_to_hdlc(dev)->proto != &proto) { /* Different proto */ + result = attach_hdlc_protocol(dev, &proto, + sizeof(struct frad_state)); + if (result) + return result; + state(hdlc)->first_pvc = NULL; + state(hdlc)->dce_pvc_count = 0; + } + memcpy(&state(hdlc)->settings, &new_settings, size); + dev->type = ARPHRD_FRAD; + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); + 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 (dev_to_hdlc(dev)->proto != &proto) /* Different proto */ + return -EINVAL; + + 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; +} + + +static int __init mod_init(void) +{ + register_hdlc_protocol(&proto); + return 0; +} + + +static void __exit mod_exit(void) +{ + unregister_hdlc_protocol(&proto); +} + + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("Frame-Relay protocol support for generic HDLC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wan/hdlc_ppp.c b/drivers/net/wan/hdlc_ppp.c new file mode 100644 index 000000000..d67623d61 --- /dev/null +++ b/drivers/net/wan/hdlc_ppp.c @@ -0,0 +1,732 @@ +/* + * Generic HDLC support routines for Linux + * Point-to-point protocol support + * + * Copyright (C) 1999 - 2008 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/errno.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define DEBUG_CP 0 /* also bytes# to dump */ +#define DEBUG_STATE 0 +#define DEBUG_HARD_HEADER 0 + +#define HDLC_ADDR_ALLSTATIONS 0xFF +#define HDLC_CTRL_UI 0x03 + +#define PID_LCP 0xC021 +#define PID_IP 0x0021 +#define PID_IPCP 0x8021 +#define PID_IPV6 0x0057 +#define PID_IPV6CP 0x8057 + +enum {IDX_LCP = 0, IDX_IPCP, IDX_IPV6CP, IDX_COUNT}; +enum {CP_CONF_REQ = 1, CP_CONF_ACK, CP_CONF_NAK, CP_CONF_REJ, CP_TERM_REQ, + CP_TERM_ACK, CP_CODE_REJ, LCP_PROTO_REJ, LCP_ECHO_REQ, LCP_ECHO_REPLY, + LCP_DISC_REQ, CP_CODES}; +#if DEBUG_CP +static const char *const code_names[CP_CODES] = { + "0", "ConfReq", "ConfAck", "ConfNak", "ConfRej", "TermReq", + "TermAck", "CodeRej", "ProtoRej", "EchoReq", "EchoReply", "Discard" +}; +static char debug_buffer[64 + 3 * DEBUG_CP]; +#endif + +enum {LCP_OPTION_MRU = 1, LCP_OPTION_ACCM, LCP_OPTION_MAGIC = 5}; + +struct hdlc_header { + u8 address; + u8 control; + __be16 protocol; +}; + +struct cp_header { + u8 code; + u8 id; + __be16 len; +}; + + +struct proto { + struct net_device *dev; + struct timer_list timer; + unsigned long timeout; + u16 pid; /* protocol ID */ + u8 state; + u8 cr_id; /* ID of last Configuration-Request */ + u8 restart_counter; +}; + +struct ppp { + struct proto protos[IDX_COUNT]; + spinlock_t lock; + unsigned long last_pong; + unsigned int req_timeout, cr_retries, term_retries; + unsigned int keepalive_interval, keepalive_timeout; + u8 seq; /* local sequence number for requests */ + u8 echo_id; /* ID of last Echo-Request (LCP) */ +}; + +enum {CLOSED = 0, STOPPED, STOPPING, REQ_SENT, ACK_RECV, ACK_SENT, OPENED, + STATES, STATE_MASK = 0xF}; +enum {START = 0, STOP, TO_GOOD, TO_BAD, RCR_GOOD, RCR_BAD, RCA, RCN, RTR, RTA, + RUC, RXJ_GOOD, RXJ_BAD, EVENTS}; +enum {INV = 0x10, IRC = 0x20, ZRC = 0x40, SCR = 0x80, SCA = 0x100, + SCN = 0x200, STR = 0x400, STA = 0x800, SCJ = 0x1000}; + +#if DEBUG_STATE +static const char *const state_names[STATES] = { + "Closed", "Stopped", "Stopping", "ReqSent", "AckRecv", "AckSent", + "Opened" +}; +static const char *const event_names[EVENTS] = { + "Start", "Stop", "TO+", "TO-", "RCR+", "RCR-", "RCA", "RCN", + "RTR", "RTA", "RUC", "RXJ+", "RXJ-" +}; +#endif + +static struct sk_buff_head tx_queue; /* used when holding the spin lock */ + +static int ppp_ioctl(struct net_device *dev, struct ifreq *ifr); + +static inline struct ppp* get_ppp(struct net_device *dev) +{ + return (struct ppp *)dev_to_hdlc(dev)->state; +} + +static inline struct proto* get_proto(struct net_device *dev, u16 pid) +{ + struct ppp *ppp = get_ppp(dev); + + switch (pid) { + case PID_LCP: + return &ppp->protos[IDX_LCP]; + case PID_IPCP: + return &ppp->protos[IDX_IPCP]; + case PID_IPV6CP: + return &ppp->protos[IDX_IPV6CP]; + default: + return NULL; + } +} + +static inline const char* proto_name(u16 pid) +{ + switch (pid) { + case PID_LCP: + return "LCP"; + case PID_IPCP: + return "IPCP"; + case PID_IPV6CP: + return "IPV6CP"; + default: + return NULL; + } +} + +static __be16 ppp_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + struct hdlc_header *data = (struct hdlc_header*)skb->data; + + if (skb->len < sizeof(struct hdlc_header)) + return htons(ETH_P_HDLC); + if (data->address != HDLC_ADDR_ALLSTATIONS || + data->control != HDLC_CTRL_UI) + return htons(ETH_P_HDLC); + + switch (data->protocol) { + case cpu_to_be16(PID_IP): + skb_pull(skb, sizeof(struct hdlc_header)); + return htons(ETH_P_IP); + + case cpu_to_be16(PID_IPV6): + skb_pull(skb, sizeof(struct hdlc_header)); + return htons(ETH_P_IPV6); + + default: + return htons(ETH_P_HDLC); + } +} + + +static int ppp_hard_header(struct sk_buff *skb, struct net_device *dev, + u16 type, const void *daddr, const void *saddr, + unsigned int len) +{ + struct hdlc_header *data; +#if DEBUG_HARD_HEADER + printk(KERN_DEBUG "%s: ppp_hard_header() called\n", dev->name); +#endif + + skb_push(skb, sizeof(struct hdlc_header)); + data = (struct hdlc_header*)skb->data; + + data->address = HDLC_ADDR_ALLSTATIONS; + data->control = HDLC_CTRL_UI; + switch (type) { + case ETH_P_IP: + data->protocol = htons(PID_IP); + break; + case ETH_P_IPV6: + data->protocol = htons(PID_IPV6); + break; + case PID_LCP: + case PID_IPCP: + case PID_IPV6CP: + data->protocol = htons(type); + break; + default: /* unknown protocol */ + data->protocol = 0; + } + return sizeof(struct hdlc_header); +} + + +static void ppp_tx_flush(void) +{ + struct sk_buff *skb; + while ((skb = skb_dequeue(&tx_queue)) != NULL) + dev_queue_xmit(skb); +} + +static void ppp_tx_cp(struct net_device *dev, u16 pid, u8 code, + u8 id, unsigned int len, const void *data) +{ + struct sk_buff *skb; + struct cp_header *cp; + unsigned int magic_len = 0; + static u32 magic; + +#if DEBUG_CP + int i; + char *ptr; +#endif + + if (pid == PID_LCP && (code == LCP_ECHO_REQ || code == LCP_ECHO_REPLY)) + magic_len = sizeof(magic); + + skb = dev_alloc_skb(sizeof(struct hdlc_header) + + sizeof(struct cp_header) + magic_len + len); + if (!skb) { + netdev_warn(dev, "out of memory in ppp_tx_cp()\n"); + return; + } + skb_reserve(skb, sizeof(struct hdlc_header)); + + cp = skb_put(skb, sizeof(struct cp_header)); + cp->code = code; + cp->id = id; + cp->len = htons(sizeof(struct cp_header) + magic_len + len); + + if (magic_len) + skb_put_data(skb, &magic, magic_len); + if (len) + skb_put_data(skb, data, len); + +#if DEBUG_CP + BUG_ON(code >= CP_CODES); + ptr = debug_buffer; + *ptr = '\x0'; + for (i = 0; i < min_t(unsigned int, magic_len + len, DEBUG_CP); i++) { + sprintf(ptr, " %02X", skb->data[sizeof(struct cp_header) + i]); + ptr += strlen(ptr); + } + printk(KERN_DEBUG "%s: TX %s [%s id 0x%X]%s\n", dev->name, + proto_name(pid), code_names[code], id, debug_buffer); +#endif + + ppp_hard_header(skb, dev, pid, NULL, NULL, 0); + + skb->priority = TC_PRIO_CONTROL; + skb->dev = dev; + skb->protocol = htons(ETH_P_HDLC); + skb_reset_network_header(skb); + skb_queue_tail(&tx_queue, skb); +} + + +/* State transition table (compare STD-51) + Events Actions + TO+ = Timeout with counter > 0 irc = Initialize-Restart-Count + TO- = Timeout with counter expired zrc = Zero-Restart-Count + + RCR+ = Receive-Configure-Request (Good) scr = Send-Configure-Request + RCR- = Receive-Configure-Request (Bad) + RCA = Receive-Configure-Ack sca = Send-Configure-Ack + RCN = Receive-Configure-Nak/Rej scn = Send-Configure-Nak/Rej + + RTR = Receive-Terminate-Request str = Send-Terminate-Request + RTA = Receive-Terminate-Ack sta = Send-Terminate-Ack + + RUC = Receive-Unknown-Code scj = Send-Code-Reject + RXJ+ = Receive-Code-Reject (permitted) + or Receive-Protocol-Reject + RXJ- = Receive-Code-Reject (catastrophic) + or Receive-Protocol-Reject +*/ +static int cp_table[EVENTS][STATES] = { + /* CLOSED STOPPED STOPPING REQ_SENT ACK_RECV ACK_SENT OPENED + 0 1 2 3 4 5 6 */ + {IRC|SCR|3, INV , INV , INV , INV , INV , INV }, /* START */ + { INV , 0 , 0 , 0 , 0 , 0 , 0 }, /* STOP */ + { INV , INV ,STR|2, SCR|3 ,SCR|3, SCR|5 , INV }, /* TO+ */ + { INV , INV , 1 , 1 , 1 , 1 , INV }, /* TO- */ + { STA|0 ,IRC|SCR|SCA|5, 2 , SCA|5 ,SCA|6, SCA|5 ,SCR|SCA|5}, /* RCR+ */ + { STA|0 ,IRC|SCR|SCN|3, 2 , SCN|3 ,SCN|4, SCN|3 ,SCR|SCN|3}, /* RCR- */ + { STA|0 , STA|1 , 2 , IRC|4 ,SCR|3, 6 , SCR|3 }, /* RCA */ + { STA|0 , STA|1 , 2 ,IRC|SCR|3,SCR|3,IRC|SCR|5, SCR|3 }, /* RCN */ + { STA|0 , STA|1 ,STA|2, STA|3 ,STA|3, STA|3 ,ZRC|STA|2}, /* RTR */ + { 0 , 1 , 1 , 3 , 3 , 5 , SCR|3 }, /* RTA */ + { SCJ|0 , SCJ|1 ,SCJ|2, SCJ|3 ,SCJ|4, SCJ|5 , SCJ|6 }, /* RUC */ + { 0 , 1 , 2 , 3 , 3 , 5 , 6 }, /* RXJ+ */ + { 0 , 1 , 1 , 1 , 1 , 1 ,IRC|STR|2}, /* RXJ- */ +}; + + +/* SCA: RCR+ must supply id, len and data + SCN: RCR- must supply code, id, len and data + STA: RTR must supply id + SCJ: RUC must supply CP packet len and data */ +static void ppp_cp_event(struct net_device *dev, u16 pid, u16 event, u8 code, + u8 id, unsigned int len, const void *data) +{ + int old_state, action; + struct ppp *ppp = get_ppp(dev); + struct proto *proto = get_proto(dev, pid); + + old_state = proto->state; + BUG_ON(old_state >= STATES); + BUG_ON(event >= EVENTS); + +#if DEBUG_STATE + printk(KERN_DEBUG "%s: %s ppp_cp_event(%s) %s ...\n", dev->name, + proto_name(pid), event_names[event], state_names[proto->state]); +#endif + + action = cp_table[event][old_state]; + + proto->state = action & STATE_MASK; + if (action & (SCR | STR)) /* set Configure-Req/Terminate-Req timer */ + mod_timer(&proto->timer, proto->timeout = + jiffies + ppp->req_timeout * HZ); + if (action & ZRC) + proto->restart_counter = 0; + if (action & IRC) + proto->restart_counter = (proto->state == STOPPING) ? + ppp->term_retries : ppp->cr_retries; + + if (action & SCR) /* send Configure-Request */ + ppp_tx_cp(dev, pid, CP_CONF_REQ, proto->cr_id = ++ppp->seq, + 0, NULL); + if (action & SCA) /* send Configure-Ack */ + ppp_tx_cp(dev, pid, CP_CONF_ACK, id, len, data); + if (action & SCN) /* send Configure-Nak/Reject */ + ppp_tx_cp(dev, pid, code, id, len, data); + if (action & STR) /* send Terminate-Request */ + ppp_tx_cp(dev, pid, CP_TERM_REQ, ++ppp->seq, 0, NULL); + if (action & STA) /* send Terminate-Ack */ + ppp_tx_cp(dev, pid, CP_TERM_ACK, id, 0, NULL); + if (action & SCJ) /* send Code-Reject */ + ppp_tx_cp(dev, pid, CP_CODE_REJ, ++ppp->seq, len, data); + + if (old_state != OPENED && proto->state == OPENED) { + netdev_info(dev, "%s up\n", proto_name(pid)); + if (pid == PID_LCP) { + netif_dormant_off(dev); + ppp_cp_event(dev, PID_IPCP, START, 0, 0, 0, NULL); + ppp_cp_event(dev, PID_IPV6CP, START, 0, 0, 0, NULL); + ppp->last_pong = jiffies; + mod_timer(&proto->timer, proto->timeout = + jiffies + ppp->keepalive_interval * HZ); + } + } + if (old_state == OPENED && proto->state != OPENED) { + netdev_info(dev, "%s down\n", proto_name(pid)); + if (pid == PID_LCP) { + netif_dormant_on(dev); + ppp_cp_event(dev, PID_IPCP, STOP, 0, 0, 0, NULL); + ppp_cp_event(dev, PID_IPV6CP, STOP, 0, 0, 0, NULL); + } + } + if (old_state != CLOSED && proto->state == CLOSED) + del_timer(&proto->timer); + +#if DEBUG_STATE + printk(KERN_DEBUG "%s: %s ppp_cp_event(%s) ... %s\n", dev->name, + proto_name(pid), event_names[event], state_names[proto->state]); +#endif +} + + +static void ppp_cp_parse_cr(struct net_device *dev, u16 pid, u8 id, + unsigned int req_len, const u8 *data) +{ + static u8 const valid_accm[6] = { LCP_OPTION_ACCM, 6, 0, 0, 0, 0 }; + const u8 *opt; + u8 *out; + unsigned int len = req_len, nak_len = 0, rej_len = 0; + + if (!(out = kmalloc(len, GFP_ATOMIC))) { + dev->stats.rx_dropped++; + return; /* out of memory, ignore CR packet */ + } + + for (opt = data; len; len -= opt[1], opt += opt[1]) { + if (len < 2 || opt[1] < 2 || len < opt[1]) + goto err_out; + + if (pid == PID_LCP) + switch (opt[0]) { + case LCP_OPTION_MRU: + continue; /* MRU always OK and > 1500 bytes? */ + + case LCP_OPTION_ACCM: /* async control character map */ + if (opt[1] < sizeof(valid_accm)) + goto err_out; + if (!memcmp(opt, valid_accm, + sizeof(valid_accm))) + continue; + if (!rej_len) { /* NAK it */ + memcpy(out + nak_len, valid_accm, + sizeof(valid_accm)); + nak_len += sizeof(valid_accm); + continue; + } + break; + case LCP_OPTION_MAGIC: + if (len < 6) + goto err_out; + if (opt[1] != 6 || (!opt[2] && !opt[3] && + !opt[4] && !opt[5])) + break; /* reject invalid magic number */ + continue; + } + /* reject this option */ + memcpy(out + rej_len, opt, opt[1]); + rej_len += opt[1]; + } + + if (rej_len) + ppp_cp_event(dev, pid, RCR_BAD, CP_CONF_REJ, id, rej_len, out); + else if (nak_len) + ppp_cp_event(dev, pid, RCR_BAD, CP_CONF_NAK, id, nak_len, out); + else + ppp_cp_event(dev, pid, RCR_GOOD, CP_CONF_ACK, id, req_len, data); + + kfree(out); + return; + +err_out: + dev->stats.rx_errors++; + kfree(out); +} + +static int ppp_rx(struct sk_buff *skb) +{ + struct hdlc_header *hdr = (struct hdlc_header*)skb->data; + struct net_device *dev = skb->dev; + struct ppp *ppp = get_ppp(dev); + struct proto *proto; + struct cp_header *cp; + unsigned long flags; + unsigned int len; + u16 pid; +#if DEBUG_CP + int i; + char *ptr; +#endif + + spin_lock_irqsave(&ppp->lock, flags); + /* Check HDLC header */ + if (skb->len < sizeof(struct hdlc_header)) + goto rx_error; + cp = skb_pull(skb, sizeof(struct hdlc_header)); + if (hdr->address != HDLC_ADDR_ALLSTATIONS || + hdr->control != HDLC_CTRL_UI) + goto rx_error; + + pid = ntohs(hdr->protocol); + proto = get_proto(dev, pid); + if (!proto) { + if (ppp->protos[IDX_LCP].state == OPENED) + ppp_tx_cp(dev, PID_LCP, LCP_PROTO_REJ, + ++ppp->seq, skb->len + 2, &hdr->protocol); + goto rx_error; + } + + len = ntohs(cp->len); + if (len < sizeof(struct cp_header) /* no complete CP header? */ || + skb->len < len /* truncated packet? */) + goto rx_error; + skb_pull(skb, sizeof(struct cp_header)); + len -= sizeof(struct cp_header); + + /* HDLC and CP headers stripped from skb */ +#if DEBUG_CP + if (cp->code < CP_CODES) + sprintf(debug_buffer, "[%s id 0x%X]", code_names[cp->code], + cp->id); + else + sprintf(debug_buffer, "[code %u id 0x%X]", cp->code, cp->id); + ptr = debug_buffer + strlen(debug_buffer); + for (i = 0; i < min_t(unsigned int, len, DEBUG_CP); i++) { + sprintf(ptr, " %02X", skb->data[i]); + ptr += strlen(ptr); + } + printk(KERN_DEBUG "%s: RX %s %s\n", dev->name, proto_name(pid), + debug_buffer); +#endif + + /* LCP only */ + if (pid == PID_LCP) + switch (cp->code) { + case LCP_PROTO_REJ: + pid = ntohs(*(__be16*)skb->data); + if (pid == PID_LCP || pid == PID_IPCP || + pid == PID_IPV6CP) + ppp_cp_event(dev, pid, RXJ_BAD, 0, 0, + 0, NULL); + goto out; + + case LCP_ECHO_REQ: /* send Echo-Reply */ + if (len >= 4 && proto->state == OPENED) + ppp_tx_cp(dev, PID_LCP, LCP_ECHO_REPLY, + cp->id, len - 4, skb->data + 4); + goto out; + + case LCP_ECHO_REPLY: + if (cp->id == ppp->echo_id) + ppp->last_pong = jiffies; + goto out; + + case LCP_DISC_REQ: /* discard */ + goto out; + } + + /* LCP, IPCP and IPV6CP */ + switch (cp->code) { + case CP_CONF_REQ: + ppp_cp_parse_cr(dev, pid, cp->id, len, skb->data); + break; + + case CP_CONF_ACK: + if (cp->id == proto->cr_id) + ppp_cp_event(dev, pid, RCA, 0, 0, 0, NULL); + break; + + case CP_CONF_REJ: + case CP_CONF_NAK: + if (cp->id == proto->cr_id) + ppp_cp_event(dev, pid, RCN, 0, 0, 0, NULL); + break; + + case CP_TERM_REQ: + ppp_cp_event(dev, pid, RTR, 0, cp->id, 0, NULL); + break; + + case CP_TERM_ACK: + ppp_cp_event(dev, pid, RTA, 0, 0, 0, NULL); + break; + + case CP_CODE_REJ: + ppp_cp_event(dev, pid, RXJ_BAD, 0, 0, 0, NULL); + break; + + default: + len += sizeof(struct cp_header); + if (len > dev->mtu) + len = dev->mtu; + ppp_cp_event(dev, pid, RUC, 0, 0, len, cp); + break; + } + goto out; + +rx_error: + dev->stats.rx_errors++; +out: + spin_unlock_irqrestore(&ppp->lock, flags); + dev_kfree_skb_any(skb); + ppp_tx_flush(); + return NET_RX_DROP; +} + +static void ppp_timer(struct timer_list *t) +{ + struct proto *proto = from_timer(proto, t, timer); + struct ppp *ppp = get_ppp(proto->dev); + unsigned long flags; + + spin_lock_irqsave(&ppp->lock, flags); + /* mod_timer could be called after we entered this function but + * before we got the lock. + */ + if (timer_pending(&proto->timer)) { + spin_unlock_irqrestore(&ppp->lock, flags); + return; + } + switch (proto->state) { + case STOPPING: + case REQ_SENT: + case ACK_RECV: + case ACK_SENT: + if (proto->restart_counter) { + ppp_cp_event(proto->dev, proto->pid, TO_GOOD, 0, 0, + 0, NULL); + proto->restart_counter--; + } else if (netif_carrier_ok(proto->dev)) + ppp_cp_event(proto->dev, proto->pid, TO_GOOD, 0, 0, + 0, NULL); + else + ppp_cp_event(proto->dev, proto->pid, TO_BAD, 0, 0, + 0, NULL); + break; + + case OPENED: + if (proto->pid != PID_LCP) + break; + if (time_after(jiffies, ppp->last_pong + + ppp->keepalive_timeout * HZ)) { + netdev_info(proto->dev, "Link down\n"); + ppp_cp_event(proto->dev, PID_LCP, STOP, 0, 0, 0, NULL); + ppp_cp_event(proto->dev, PID_LCP, START, 0, 0, 0, NULL); + } else { /* send keep-alive packet */ + ppp->echo_id = ++ppp->seq; + ppp_tx_cp(proto->dev, PID_LCP, LCP_ECHO_REQ, + ppp->echo_id, 0, NULL); + proto->timer.expires = jiffies + + ppp->keepalive_interval * HZ; + add_timer(&proto->timer); + } + break; + } + spin_unlock_irqrestore(&ppp->lock, flags); + ppp_tx_flush(); +} + + +static void ppp_start(struct net_device *dev) +{ + struct ppp *ppp = get_ppp(dev); + int i; + + for (i = 0; i < IDX_COUNT; i++) { + struct proto *proto = &ppp->protos[i]; + proto->dev = dev; + timer_setup(&proto->timer, ppp_timer, 0); + proto->state = CLOSED; + } + ppp->protos[IDX_LCP].pid = PID_LCP; + ppp->protos[IDX_IPCP].pid = PID_IPCP; + ppp->protos[IDX_IPV6CP].pid = PID_IPV6CP; + + ppp_cp_event(dev, PID_LCP, START, 0, 0, 0, NULL); +} + +static void ppp_stop(struct net_device *dev) +{ + ppp_cp_event(dev, PID_LCP, STOP, 0, 0, 0, NULL); +} + +static void ppp_close(struct net_device *dev) +{ + ppp_tx_flush(); +} + +static struct hdlc_proto proto = { + .start = ppp_start, + .stop = ppp_stop, + .close = ppp_close, + .type_trans = ppp_type_trans, + .ioctl = ppp_ioctl, + .netif_rx = ppp_rx, + .module = THIS_MODULE, +}; + +static const struct header_ops ppp_header_ops = { + .create = ppp_hard_header, +}; + +static int ppp_ioctl(struct net_device *dev, struct ifreq *ifr) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + struct ppp *ppp; + int result; + + switch (ifr->ifr_settings.type) { + case IF_GET_PROTO: + if (dev_to_hdlc(dev)->proto != &proto) + return -EINVAL; + 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; + + result = attach_hdlc_protocol(dev, &proto, sizeof(struct ppp)); + if (result) + return result; + + ppp = get_ppp(dev); + spin_lock_init(&ppp->lock); + ppp->req_timeout = 2; + ppp->cr_retries = 10; + ppp->term_retries = 2; + ppp->keepalive_interval = 10; + ppp->keepalive_timeout = 60; + + dev->hard_header_len = sizeof(struct hdlc_header); + dev->header_ops = &ppp_header_ops; + dev->type = ARPHRD_PPP; + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); + netif_dormant_on(dev); + return 0; + } + + return -EINVAL; +} + + +static int __init mod_init(void) +{ + skb_queue_head_init(&tx_queue); + register_hdlc_protocol(&proto); + return 0; +} + +static void __exit mod_exit(void) +{ + unregister_hdlc_protocol(&proto); +} + + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("PPP protocol support for generic HDLC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wan/hdlc_raw.c b/drivers/net/wan/hdlc_raw.c new file mode 100644 index 000000000..4feb45001 --- /dev/null +++ b/drivers/net/wan/hdlc_raw.c @@ -0,0 +1,115 @@ +/* + * Generic HDLC support routines for Linux + * HDLC support + * + * Copyright (C) 1999 - 2006 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/errno.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.h> + + +static int raw_ioctl(struct net_device *dev, struct ifreq *ifr); + +static __be16 raw_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + return cpu_to_be16(ETH_P_IP); +} + +static struct hdlc_proto proto = { + .type_trans = raw_type_trans, + .ioctl = raw_ioctl, + .module = THIS_MODULE, +}; + + +static int 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: + if (dev_to_hdlc(dev)->proto != &proto) + return -EINVAL; + 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, 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; + + result = attach_hdlc_protocol(dev, &proto, + sizeof(raw_hdlc_proto)); + if (result) + return result; + memcpy(hdlc->state, &new_settings, size); + dev->type = ARPHRD_RAWHDLC; + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); + netif_dormant_off(dev); + return 0; + } + + return -EINVAL; +} + + +static int __init mod_init(void) +{ + register_hdlc_protocol(&proto); + return 0; +} + + + +static void __exit mod_exit(void) +{ + unregister_hdlc_protocol(&proto); +} + + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("Raw HDLC protocol support for generic HDLC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wan/hdlc_raw_eth.c b/drivers/net/wan/hdlc_raw_eth.c new file mode 100644 index 000000000..676dea291 --- /dev/null +++ b/drivers/net/wan/hdlc_raw_eth.c @@ -0,0 +1,135 @@ +/* + * Generic HDLC support routines for Linux + * HDLC Ethernet emulation support + * + * Copyright (C) 2002-2006 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/errno.h> +#include <linux/etherdevice.h> +#include <linux/gfp.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.h> + +static int raw_eth_ioctl(struct net_device *dev, struct ifreq *ifr); + +static netdev_tx_t 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)) { + dev->stats.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); +} + + +static struct hdlc_proto proto = { + .type_trans = eth_type_trans, + .xmit = eth_tx, + .ioctl = raw_eth_ioctl, + .module = THIS_MODULE, +}; + + +static int 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); + unsigned int old_qlen; + int result; + + switch (ifr->ifr_settings.type) { + case IF_GET_PROTO: + if (dev_to_hdlc(dev)->proto != &proto) + return -EINVAL; + 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, 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; + + result = attach_hdlc_protocol(dev, &proto, + sizeof(raw_hdlc_proto)); + if (result) + return result; + memcpy(hdlc->state, &new_settings, size); + old_qlen = dev->tx_queue_len; + ether_setup(dev); + dev->tx_queue_len = old_qlen; + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + eth_hw_addr_random(dev); + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); + netif_dormant_off(dev); + return 0; + } + + return -EINVAL; +} + + +static int __init mod_init(void) +{ + register_hdlc_protocol(&proto); + return 0; +} + + + +static void __exit mod_exit(void) +{ + unregister_hdlc_protocol(&proto); +} + + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("Ethernet encapsulation support for generic HDLC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wan/hdlc_x25.c b/drivers/net/wan/hdlc_x25.c new file mode 100644 index 000000000..e86763806 --- /dev/null +++ b/drivers/net/wan/hdlc_x25.c @@ -0,0 +1,244 @@ +/* + * Generic HDLC support routines for Linux + * X.25 support + * + * Copyright (C) 1999 - 2006 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/errno.h> +#include <linux/gfp.h> +#include <linux/hdlc.h> +#include <linux/if_arp.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/lapb.h> +#include <linux/module.h> +#include <linux/pkt_sched.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.h> +#include <net/x25device.h> + +static int x25_ioctl(struct net_device *dev, struct ifreq *ifr); + +/* 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) { + netdev_err(dev, "out of memory\n"); + 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, X25_IFACE_CONNECT); +} + + + +static void x25_disconnected(struct net_device *dev, int reason) +{ + x25_connect_disconnect(dev, reason, X25_IFACE_DISCONNECT); +} + + + +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 = X25_IFACE_DATA; + + 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 netdev_tx_t x25_xmit(struct sk_buff *skb, struct net_device *dev) +{ + int result; + + + /* X.25 to LAPB */ + switch (skb->data[0]) { + case X25_IFACE_DATA: /* Data to be transmitted */ + skb_pull(skb, 1); + if ((result = lapb_data_request(dev, skb)) != LAPB_OK) + dev_kfree_skb(skb); + return NETDEV_TX_OK; + + case X25_IFACE_CONNECT: + if ((result = lapb_connect_request(dev))!= LAPB_OK) { + if (result == LAPB_CONNECTED) + /* Send connect confirm. msg to level 3 */ + x25_connected(dev, 0); + else + netdev_err(dev, "LAPB connect request failed, error code = %i\n", + result); + } + break; + + case X25_IFACE_DISCONNECT: + if ((result = lapb_disconnect_request(dev)) != LAPB_OK) { + if (result == LAPB_NOTCONNECTED) + /* Send disconnect confirm. msg to level 3 */ + x25_disconnected(dev, 0); + else + netdev_err(dev, "LAPB disconnect request failed, error code = %i\n", + result); + } + break; + + default: /* to be defined */ + break; + } + + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + + + +static int x25_open(struct net_device *dev) +{ + int result; + static const struct lapb_register_struct cb = { + .connect_confirmation = x25_connected, + .connect_indication = x25_connected, + .disconnect_confirmation = x25_disconnected, + .disconnect_indication = x25_disconnected, + .data_indication = x25_data_indication, + .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) +{ + struct net_device *dev = skb->dev; + + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { + dev->stats.rx_dropped++; + return NET_RX_DROP; + } + + if (lapb_data_received(dev, skb) == LAPB_OK) + return NET_RX_SUCCESS; + + dev->stats.rx_errors++; + dev_kfree_skb_any(skb); + return NET_RX_DROP; +} + + +static struct hdlc_proto proto = { + .open = x25_open, + .close = x25_close, + .ioctl = x25_ioctl, + .netif_rx = x25_rx, + .xmit = x25_xmit, + .module = THIS_MODULE, +}; + + +static int 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: + if (dev_to_hdlc(dev)->proto != &proto) + return -EINVAL; + 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; + + if ((result = attach_hdlc_protocol(dev, &proto, 0))) + return result; + dev->type = ARPHRD_X25; + call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); + netif_dormant_off(dev); + return 0; + } + + return -EINVAL; +} + + +static int __init mod_init(void) +{ + register_hdlc_protocol(&proto); + return 0; +} + + + +static void __exit mod_exit(void) +{ + unregister_hdlc_protocol(&proto); +} + + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("X.25 protocol support for generic HDLC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wan/hostess_sv11.c b/drivers/net/wan/hostess_sv11.c new file mode 100644 index 000000000..4de0737fb --- /dev/null +++ b/drivers/net/wan/hostess_sv11.c @@ -0,0 +1,351 @@ +/* + * 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 isn't wired (why - beats me) + * + * Generic HDLC port Copyright (C) 2008 Krzysztof Halasa <khc@pm.waw.pl> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/hdlc.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <net/arp.h> + +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> +#include "z85230.h" + +static int dma; + +/* + * Network driver support routines + */ + +static inline struct z8530_dev* dev_to_sv(struct net_device *dev) +{ + return (struct z8530_dev *)dev_to_hdlc(dev)->priv; +} + +/* + * Frame receive. Simple for our card as we do HDLC 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 = hdlc_type_trans(skb, c->netdevice); + skb_reset_mac_header(skb); + skb->dev = c->netdevice; + /* + * Send it to the PPP layer. We don't have time to process + * it right now. + */ + netif_rx(skb); +} + +/* + * We've been placed in the UP state + */ + +static int hostess_open(struct net_device *d) +{ + struct z8530_dev *sv11 = dev_to_sv(d); + int err = -1; + + /* + * Link layer up + */ + switch (dma) { + case 0: + err = z8530_sync_open(d, &sv11->chanA); + break; + case 1: + err = z8530_sync_dma_open(d, &sv11->chanA); + break; + case 2: + err = z8530_sync_txdma_open(d, &sv11->chanA); + break; + } + + if (err) + return err; + + err = hdlc_open(d); + if (err) { + switch (dma) { + case 0: + z8530_sync_close(d, &sv11->chanA); + break; + case 1: + z8530_sync_dma_close(d, &sv11->chanA); + break; + case 2: + z8530_sync_txdma_close(d, &sv11->chanA); + break; + } + return err; + } + sv11->chanA.rx_function = hostess_input; + + /* + * Go go go + */ + + netif_start_queue(d); + return 0; +} + +static int hostess_close(struct net_device *d) +{ + struct z8530_dev *sv11 = dev_to_sv(d); + /* + * Discard new frames + */ + sv11->chanA.rx_function = z8530_null_rx; + + hdlc_close(d); + netif_stop_queue(d); + + switch (dma) { + case 0: + z8530_sync_close(d, &sv11->chanA); + break; + case 1: + z8530_sync_dma_close(d, &sv11->chanA); + break; + case 2: + z8530_sync_txdma_close(d, &sv11->chanA); + break; + } + return 0; +} + +static int hostess_ioctl(struct net_device *d, struct ifreq *ifr, int cmd) +{ + /* struct z8530_dev *sv11=dev_to_sv(d); + z8530_ioctl(d,&sv11->chanA,ifr,cmd) */ + return hdlc_ioctl(d, ifr, cmd); +} + +/* + * Passed network frames, fire them downwind. + */ + +static netdev_tx_t hostess_queue_xmit(struct sk_buff *skb, + struct net_device *d) +{ + return z8530_queue_xmit(&dev_to_sv(d)->chanA, skb); +} + +static int hostess_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + if (encoding == ENCODING_NRZ && parity == PARITY_CRC16_PR1_CCITT) + return 0; + return -EINVAL; +} + +/* + * Description block for a Comtrol Hostess SV11 card + */ + +static const struct net_device_ops hostess_ops = { + .ndo_open = hostess_open, + .ndo_stop = hostess_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = hostess_ioctl, +}; + +static struct z8530_dev *sv11_init(int iobase, int irq) +{ + struct z8530_dev *sv; + struct net_device *netdev; + /* + * Get the needed I/O space + */ + + if (!request_region(iobase, 8, "Comtrol SV11")) { + pr_warn("I/O 0x%X already in use\n", iobase); + return NULL; + } + + sv = kzalloc(sizeof(struct z8530_dev), GFP_KERNEL); + if (!sv) + goto err_kzalloc; + + /* + * Stuff in the I/O addressing + */ + + sv->active = 0; + + sv->chanA.ctrlio = iobase + 1; + sv->chanA.dataio = iobase + 3; + sv->chanB.ctrlio = -1; + sv->chanB.dataio = -1; + sv->chanA.irqs = &z8530_nop; + sv->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, 0, + "Hostess SV11", sv) < 0) { + pr_warn("IRQ %d already in use\n", irq); + goto err_irq; + } + + sv->irq = irq; + sv->chanA.private = sv; + sv->chanA.dev = sv; + sv->chanB.dev = sv; + + if (dma) { + /* + * You can have DMA off or 1 and 3 thats the lot + * on the Comtrol. + */ + sv->chanA.txdma = 3; + sv->chanA.rxdma = 1; + outb(0x03 | 0x08, iobase + 4); /* DMA on */ + if (request_dma(sv->chanA.txdma, "Hostess SV/11 (TX)")) + goto err_txdma; + + if (dma == 1) + if (request_dma(sv->chanA.rxdma, "Hostess SV/11 (RX)")) + goto err_rxdma; + } + + /* 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(sv)) { + pr_err("Z8530 series device not found\n"); + enable_irq(irq); + goto free_dma; + } + z8530_channel_load(&sv->chanB, z8530_dead_port); + if (sv->type == Z85C30) + z8530_channel_load(&sv->chanA, z8530_hdlc_kilostream); + else + z8530_channel_load(&sv->chanA, z8530_hdlc_kilostream_85230); + + enable_irq(irq); + + /* + * Now we can take the IRQ + */ + + sv->chanA.netdevice = netdev = alloc_hdlcdev(sv); + if (!netdev) + goto free_dma; + + dev_to_hdlc(netdev)->attach = hostess_attach; + dev_to_hdlc(netdev)->xmit = hostess_queue_xmit; + netdev->netdev_ops = &hostess_ops; + netdev->base_addr = iobase; + netdev->irq = irq; + + if (register_hdlc_device(netdev)) { + pr_err("unable to register HDLC device\n"); + free_netdev(netdev); + goto free_dma; + } + + z8530_describe(sv, "I/O", iobase); + sv->active = 1; + return sv; + +free_dma: + if (dma == 1) + free_dma(sv->chanA.rxdma); +err_rxdma: + if (dma) + free_dma(sv->chanA.txdma); +err_txdma: + free_irq(irq, sv); +err_irq: + kfree(sv); +err_kzalloc: + release_region(iobase, 8); + return NULL; +} + +static void sv11_shutdown(struct z8530_dev *dev) +{ + unregister_hdlc_device(dev->chanA.netdevice); + z8530_shutdown(dev); + free_irq(dev->irq, dev); + if (dma) { + if (dma == 1) + free_dma(dev->chanA.rxdma); + free_dma(dev->chanA.txdma); + } + release_region(dev->chanA.ctrlio - 1, 8); + free_netdev(dev->chanA.netdevice); + kfree(dev); +} + +static int io = 0x200; +static int irq = 9; + +module_param_hw(io, int, ioport, 0); +MODULE_PARM_DESC(io, "The I/O base of the Comtrol Hostess SV11 card"); +module_param_hw(dma, int, dma, 0); +MODULE_PARM_DESC(dma, "Set this to 1 to use DMA1/DMA3 for TX/RX"); +module_param_hw(irq, int, irq, 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 z8530_dev *sv11_unit; + +int init_module(void) +{ + if ((sv11_unit = sv11_init(io, irq)) == NULL) + return -ENODEV; + return 0; +} + +void cleanup_module(void) +{ + if (sv11_unit) + sv11_shutdown(sv11_unit); +} diff --git a/drivers/net/wan/ixp4xx_hss.c b/drivers/net/wan/ixp4xx_hss.c new file mode 100644 index 000000000..a269ed63d --- /dev/null +++ b/drivers/net/wan/ixp4xx_hss.c @@ -0,0 +1,1418 @@ +/* + * Intel IXP4xx HSS (synchronous serial port) driver for Linux + * + * Copyright (C) 2007-2008 Krzysztof Hałasa <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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/cdev.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/fs.h> +#include <linux/hdlc.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <mach/npe.h> +#include <mach/qmgr.h> + +#define DEBUG_DESC 0 +#define DEBUG_RX 0 +#define DEBUG_TX 0 +#define DEBUG_PKT_BYTES 0 +#define DEBUG_CLOSE 0 + +#define DRV_NAME "ixp4xx_hss" + +#define PKT_EXTRA_FLAGS 0 /* orig 1 */ +#define PKT_NUM_PIPES 1 /* 1, 2 or 4 */ +#define PKT_PIPE_FIFO_SIZEW 4 /* total 4 dwords per HSS */ + +#define RX_DESCS 16 /* also length of all RX queues */ +#define TX_DESCS 16 /* also length of all TX queues */ + +#define POOL_ALLOC_SIZE (sizeof(struct desc) * (RX_DESCS + TX_DESCS)) +#define RX_SIZE (HDLC_MAX_MRU + 4) /* NPE needs more space */ +#define MAX_CLOSE_WAIT 1000 /* microseconds */ +#define HSS_COUNT 2 +#define FRAME_SIZE 256 /* doesn't matter at this point */ +#define FRAME_OFFSET 0 +#define MAX_CHANNELS (FRAME_SIZE / 8) + +#define NAPI_WEIGHT 16 + +/* Queue IDs */ +#define HSS0_CHL_RXTRIG_QUEUE 12 /* orig size = 32 dwords */ +#define HSS0_PKT_RX_QUEUE 13 /* orig size = 32 dwords */ +#define HSS0_PKT_TX0_QUEUE 14 /* orig size = 16 dwords */ +#define HSS0_PKT_TX1_QUEUE 15 +#define HSS0_PKT_TX2_QUEUE 16 +#define HSS0_PKT_TX3_QUEUE 17 +#define HSS0_PKT_RXFREE0_QUEUE 18 /* orig size = 16 dwords */ +#define HSS0_PKT_RXFREE1_QUEUE 19 +#define HSS0_PKT_RXFREE2_QUEUE 20 +#define HSS0_PKT_RXFREE3_QUEUE 21 +#define HSS0_PKT_TXDONE_QUEUE 22 /* orig size = 64 dwords */ + +#define HSS1_CHL_RXTRIG_QUEUE 10 +#define HSS1_PKT_RX_QUEUE 0 +#define HSS1_PKT_TX0_QUEUE 5 +#define HSS1_PKT_TX1_QUEUE 6 +#define HSS1_PKT_TX2_QUEUE 7 +#define HSS1_PKT_TX3_QUEUE 8 +#define HSS1_PKT_RXFREE0_QUEUE 1 +#define HSS1_PKT_RXFREE1_QUEUE 2 +#define HSS1_PKT_RXFREE2_QUEUE 3 +#define HSS1_PKT_RXFREE3_QUEUE 4 +#define HSS1_PKT_TXDONE_QUEUE 9 + +#define NPE_PKT_MODE_HDLC 0 +#define NPE_PKT_MODE_RAW 1 +#define NPE_PKT_MODE_56KMODE 2 +#define NPE_PKT_MODE_56KENDIAN_MSB 4 + +/* PKT_PIPE_HDLC_CFG_WRITE flags */ +#define PKT_HDLC_IDLE_ONES 0x1 /* default = flags */ +#define PKT_HDLC_CRC_32 0x2 /* default = CRC-16 */ +#define PKT_HDLC_MSB_ENDIAN 0x4 /* default = LE */ + + +/* hss_config, PCRs */ +/* Frame sync sampling, default = active low */ +#define PCR_FRM_SYNC_ACTIVE_HIGH 0x40000000 +#define PCR_FRM_SYNC_FALLINGEDGE 0x80000000 +#define PCR_FRM_SYNC_RISINGEDGE 0xC0000000 + +/* Frame sync pin: input (default) or output generated off a given clk edge */ +#define PCR_FRM_SYNC_OUTPUT_FALLING 0x20000000 +#define PCR_FRM_SYNC_OUTPUT_RISING 0x30000000 + +/* Frame and data clock sampling on edge, default = falling */ +#define PCR_FCLK_EDGE_RISING 0x08000000 +#define PCR_DCLK_EDGE_RISING 0x04000000 + +/* Clock direction, default = input */ +#define PCR_SYNC_CLK_DIR_OUTPUT 0x02000000 + +/* Generate/Receive frame pulses, default = enabled */ +#define PCR_FRM_PULSE_DISABLED 0x01000000 + + /* Data rate is full (default) or half the configured clk speed */ +#define PCR_HALF_CLK_RATE 0x00200000 + +/* Invert data between NPE and HSS FIFOs? (default = no) */ +#define PCR_DATA_POLARITY_INVERT 0x00100000 + +/* TX/RX endianness, default = LSB */ +#define PCR_MSB_ENDIAN 0x00080000 + +/* Normal (default) / open drain mode (TX only) */ +#define PCR_TX_PINS_OPEN_DRAIN 0x00040000 + +/* No framing bit transmitted and expected on RX? (default = framing bit) */ +#define PCR_SOF_NO_FBIT 0x00020000 + +/* Drive data pins? */ +#define PCR_TX_DATA_ENABLE 0x00010000 + +/* Voice 56k type: drive the data pins low (default), high, high Z */ +#define PCR_TX_V56K_HIGH 0x00002000 +#define PCR_TX_V56K_HIGH_IMP 0x00004000 + +/* Unassigned type: drive the data pins low (default), high, high Z */ +#define PCR_TX_UNASS_HIGH 0x00000800 +#define PCR_TX_UNASS_HIGH_IMP 0x00001000 + +/* T1 @ 1.544MHz only: Fbit dictated in FIFO (default) or high Z */ +#define PCR_TX_FB_HIGH_IMP 0x00000400 + +/* 56k data endiannes - which bit unused: high (default) or low */ +#define PCR_TX_56KE_BIT_0_UNUSED 0x00000200 + +/* 56k data transmission type: 32/8 bit data (default) or 56K data */ +#define PCR_TX_56KS_56K_DATA 0x00000100 + +/* hss_config, cCR */ +/* Number of packetized clients, default = 1 */ +#define CCR_NPE_HFIFO_2_HDLC 0x04000000 +#define CCR_NPE_HFIFO_3_OR_4HDLC 0x08000000 + +/* default = no loopback */ +#define CCR_LOOPBACK 0x02000000 + +/* HSS number, default = 0 (first) */ +#define CCR_SECOND_HSS 0x01000000 + + +/* hss_config, clkCR: main:10, num:10, denom:12 */ +#define CLK42X_SPEED_EXP ((0x3FF << 22) | ( 2 << 12) | 15) /*65 KHz*/ + +#define CLK42X_SPEED_512KHZ (( 130 << 22) | ( 2 << 12) | 15) +#define CLK42X_SPEED_1536KHZ (( 43 << 22) | ( 18 << 12) | 47) +#define CLK42X_SPEED_1544KHZ (( 43 << 22) | ( 33 << 12) | 192) +#define CLK42X_SPEED_2048KHZ (( 32 << 22) | ( 34 << 12) | 63) +#define CLK42X_SPEED_4096KHZ (( 16 << 22) | ( 34 << 12) | 127) +#define CLK42X_SPEED_8192KHZ (( 8 << 22) | ( 34 << 12) | 255) + +#define CLK46X_SPEED_512KHZ (( 130 << 22) | ( 24 << 12) | 127) +#define CLK46X_SPEED_1536KHZ (( 43 << 22) | (152 << 12) | 383) +#define CLK46X_SPEED_1544KHZ (( 43 << 22) | ( 66 << 12) | 385) +#define CLK46X_SPEED_2048KHZ (( 32 << 22) | (280 << 12) | 511) +#define CLK46X_SPEED_4096KHZ (( 16 << 22) | (280 << 12) | 1023) +#define CLK46X_SPEED_8192KHZ (( 8 << 22) | (280 << 12) | 2047) + +/* + * HSS_CONFIG_CLOCK_CR register consists of 3 parts: + * A (10 bits), B (10 bits) and C (12 bits). + * IXP42x HSS clock generator operation (verified with an oscilloscope): + * Each clock bit takes 7.5 ns (1 / 133.xx MHz). + * The clock sequence consists of (C - B) states of 0s and 1s, each state is + * A bits wide. It's followed by (B + 1) states of 0s and 1s, each state is + * (A + 1) bits wide. + * + * The resulting average clock frequency (assuming 33.333 MHz oscillator) is: + * freq = 66.666 MHz / (A + (B + 1) / (C + 1)) + * minimum freq = 66.666 MHz / (A + 1) + * maximum freq = 66.666 MHz / A + * + * Example: A = 2, B = 2, C = 7, CLOCK_CR register = 2 << 22 | 2 << 12 | 7 + * freq = 66.666 MHz / (2 + (2 + 1) / (7 + 1)) = 28.07 MHz (Mb/s). + * The clock sequence is: 1100110011 (5 doubles) 000111000 (3 triples). + * The sequence takes (C - B) * A + (B + 1) * (A + 1) = 5 * 2 + 3 * 3 bits + * = 19 bits (each 7.5 ns long) = 142.5 ns (then the sequence repeats). + * The sequence consists of 4 complete clock periods, thus the average + * frequency (= clock rate) is 4 / 142.5 ns = 28.07 MHz (Mb/s). + * (max specified clock rate for IXP42x HSS is 8.192 Mb/s). + */ + +/* hss_config, LUT entries */ +#define TDMMAP_UNASSIGNED 0 +#define TDMMAP_HDLC 1 /* HDLC - packetized */ +#define TDMMAP_VOICE56K 2 /* Voice56K - 7-bit channelized */ +#define TDMMAP_VOICE64K 3 /* Voice64K - 8-bit channelized */ + +/* offsets into HSS config */ +#define HSS_CONFIG_TX_PCR 0x00 /* port configuration registers */ +#define HSS_CONFIG_RX_PCR 0x04 +#define HSS_CONFIG_CORE_CR 0x08 /* loopback control, HSS# */ +#define HSS_CONFIG_CLOCK_CR 0x0C /* clock generator control */ +#define HSS_CONFIG_TX_FCR 0x10 /* frame configuration registers */ +#define HSS_CONFIG_RX_FCR 0x14 +#define HSS_CONFIG_TX_LUT 0x18 /* channel look-up tables */ +#define HSS_CONFIG_RX_LUT 0x38 + + +/* NPE command codes */ +/* writes the ConfigWord value to the location specified by offset */ +#define PORT_CONFIG_WRITE 0x40 + +/* triggers the NPE to load the contents of the configuration table */ +#define PORT_CONFIG_LOAD 0x41 + +/* triggers the NPE to return an HssErrorReadResponse message */ +#define PORT_ERROR_READ 0x42 + +/* triggers the NPE to reset internal status and enable the HssPacketized + operation for the flow specified by pPipe */ +#define PKT_PIPE_FLOW_ENABLE 0x50 +#define PKT_PIPE_FLOW_DISABLE 0x51 +#define PKT_NUM_PIPES_WRITE 0x52 +#define PKT_PIPE_FIFO_SIZEW_WRITE 0x53 +#define PKT_PIPE_HDLC_CFG_WRITE 0x54 +#define PKT_PIPE_IDLE_PATTERN_WRITE 0x55 +#define PKT_PIPE_RX_SIZE_WRITE 0x56 +#define PKT_PIPE_MODE_WRITE 0x57 + +/* HDLC packet status values - desc->status */ +#define ERR_SHUTDOWN 1 /* stop or shutdown occurrence */ +#define ERR_HDLC_ALIGN 2 /* HDLC alignment error */ +#define ERR_HDLC_FCS 3 /* HDLC Frame Check Sum error */ +#define ERR_RXFREE_Q_EMPTY 4 /* RX-free queue became empty while receiving + this packet (if buf_len < pkt_len) */ +#define ERR_HDLC_TOO_LONG 5 /* HDLC frame size too long */ +#define ERR_HDLC_ABORT 6 /* abort sequence received */ +#define ERR_DISCONNECTING 7 /* disconnect is in progress */ + + +#ifdef __ARMEB__ +typedef struct sk_buff buffer_t; +#define free_buffer dev_kfree_skb +#define free_buffer_irq dev_kfree_skb_irq +#else +typedef void buffer_t; +#define free_buffer kfree +#define free_buffer_irq kfree +#endif + +struct port { + struct device *dev; + struct npe *npe; + struct net_device *netdev; + struct napi_struct napi; + struct hss_plat_info *plat; + buffer_t *rx_buff_tab[RX_DESCS], *tx_buff_tab[TX_DESCS]; + struct desc *desc_tab; /* coherent */ + dma_addr_t desc_tab_phys; + unsigned int id; + unsigned int clock_type, clock_rate, loopback; + unsigned int initialized, carrier; + u8 hdlc_cfg; + u32 clock_reg; +}; + +/* NPE message structure */ +struct msg { +#ifdef __ARMEB__ + u8 cmd, unused, hss_port, index; + union { + struct { u8 data8a, data8b, data8c, data8d; }; + struct { u16 data16a, data16b; }; + struct { u32 data32; }; + }; +#else + u8 index, hss_port, unused, cmd; + union { + struct { u8 data8d, data8c, data8b, data8a; }; + struct { u16 data16b, data16a; }; + struct { u32 data32; }; + }; +#endif +}; + +/* HDLC packet descriptor */ +struct desc { + u32 next; /* pointer to next buffer, unused */ + +#ifdef __ARMEB__ + u16 buf_len; /* buffer length */ + u16 pkt_len; /* packet length */ + u32 data; /* pointer to data buffer in RAM */ + u8 status; + u8 error_count; + u16 __reserved; +#else + u16 pkt_len; /* packet length */ + u16 buf_len; /* buffer length */ + u32 data; /* pointer to data buffer in RAM */ + u16 __reserved; + u8 error_count; + u8 status; +#endif + u32 __reserved1[4]; +}; + + +#define rx_desc_phys(port, n) ((port)->desc_tab_phys + \ + (n) * sizeof(struct desc)) +#define rx_desc_ptr(port, n) (&(port)->desc_tab[n]) + +#define tx_desc_phys(port, n) ((port)->desc_tab_phys + \ + ((n) + RX_DESCS) * sizeof(struct desc)) +#define tx_desc_ptr(port, n) (&(port)->desc_tab[(n) + RX_DESCS]) + +/***************************************************************************** + * global variables + ****************************************************************************/ + +static int ports_open; +static struct dma_pool *dma_pool; +static spinlock_t npe_lock; + +static const struct { + int tx, txdone, rx, rxfree; +}queue_ids[2] = {{HSS0_PKT_TX0_QUEUE, HSS0_PKT_TXDONE_QUEUE, HSS0_PKT_RX_QUEUE, + HSS0_PKT_RXFREE0_QUEUE}, + {HSS1_PKT_TX0_QUEUE, HSS1_PKT_TXDONE_QUEUE, HSS1_PKT_RX_QUEUE, + HSS1_PKT_RXFREE0_QUEUE}, +}; + +/***************************************************************************** + * utility functions + ****************************************************************************/ + +static inline struct port* dev_to_port(struct net_device *dev) +{ + return dev_to_hdlc(dev)->priv; +} + +#ifndef __ARMEB__ +static inline void memcpy_swab32(u32 *dest, u32 *src, int cnt) +{ + int i; + for (i = 0; i < cnt; i++) + dest[i] = swab32(src[i]); +} +#endif + +/***************************************************************************** + * HSS access + ****************************************************************************/ + +static void hss_npe_send(struct port *port, struct msg *msg, const char* what) +{ + u32 *val = (u32*)msg; + if (npe_send_message(port->npe, msg, what)) { + pr_crit("HSS-%i: unable to send command [%08X:%08X] to %s\n", + port->id, val[0], val[1], npe_name(port->npe)); + BUG(); + } +} + +static void hss_config_set_lut(struct port *port) +{ + struct msg msg; + int ch; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_WRITE; + msg.hss_port = port->id; + + for (ch = 0; ch < MAX_CHANNELS; ch++) { + msg.data32 >>= 2; + msg.data32 |= TDMMAP_HDLC << 30; + + if (ch % 16 == 15) { + msg.index = HSS_CONFIG_TX_LUT + ((ch / 4) & ~3); + hss_npe_send(port, &msg, "HSS_SET_TX_LUT"); + + msg.index += HSS_CONFIG_RX_LUT - HSS_CONFIG_TX_LUT; + hss_npe_send(port, &msg, "HSS_SET_RX_LUT"); + } + } +} + +static void hss_config(struct port *port) +{ + struct msg msg; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_WRITE; + msg.hss_port = port->id; + msg.index = HSS_CONFIG_TX_PCR; + msg.data32 = PCR_FRM_PULSE_DISABLED | PCR_MSB_ENDIAN | + PCR_TX_DATA_ENABLE | PCR_SOF_NO_FBIT; + if (port->clock_type == CLOCK_INT) + msg.data32 |= PCR_SYNC_CLK_DIR_OUTPUT; + hss_npe_send(port, &msg, "HSS_SET_TX_PCR"); + + msg.index = HSS_CONFIG_RX_PCR; + msg.data32 ^= PCR_TX_DATA_ENABLE | PCR_DCLK_EDGE_RISING; + hss_npe_send(port, &msg, "HSS_SET_RX_PCR"); + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_WRITE; + msg.hss_port = port->id; + msg.index = HSS_CONFIG_CORE_CR; + msg.data32 = (port->loopback ? CCR_LOOPBACK : 0) | + (port->id ? CCR_SECOND_HSS : 0); + hss_npe_send(port, &msg, "HSS_SET_CORE_CR"); + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_WRITE; + msg.hss_port = port->id; + msg.index = HSS_CONFIG_CLOCK_CR; + msg.data32 = port->clock_reg; + hss_npe_send(port, &msg, "HSS_SET_CLOCK_CR"); + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_WRITE; + msg.hss_port = port->id; + msg.index = HSS_CONFIG_TX_FCR; + msg.data16a = FRAME_OFFSET; + msg.data16b = FRAME_SIZE - 1; + hss_npe_send(port, &msg, "HSS_SET_TX_FCR"); + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_WRITE; + msg.hss_port = port->id; + msg.index = HSS_CONFIG_RX_FCR; + msg.data16a = FRAME_OFFSET; + msg.data16b = FRAME_SIZE - 1; + hss_npe_send(port, &msg, "HSS_SET_RX_FCR"); + + hss_config_set_lut(port); + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_CONFIG_LOAD; + msg.hss_port = port->id; + hss_npe_send(port, &msg, "HSS_LOAD_CONFIG"); + + if (npe_recv_message(port->npe, &msg, "HSS_LOAD_CONFIG") || + /* HSS_LOAD_CONFIG for port #1 returns port_id = #4 */ + msg.cmd != PORT_CONFIG_LOAD || msg.data32) { + pr_crit("HSS-%i: HSS_LOAD_CONFIG failed\n", port->id); + BUG(); + } + + /* HDLC may stop working without this - check FIXME */ + npe_recv_message(port->npe, &msg, "FLUSH_IT"); +} + +static void hss_set_hdlc_cfg(struct port *port) +{ + struct msg msg; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PKT_PIPE_HDLC_CFG_WRITE; + msg.hss_port = port->id; + msg.data8a = port->hdlc_cfg; /* rx_cfg */ + msg.data8b = port->hdlc_cfg | (PKT_EXTRA_FLAGS << 3); /* tx_cfg */ + hss_npe_send(port, &msg, "HSS_SET_HDLC_CFG"); +} + +static u32 hss_get_status(struct port *port) +{ + struct msg msg; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PORT_ERROR_READ; + msg.hss_port = port->id; + hss_npe_send(port, &msg, "PORT_ERROR_READ"); + if (npe_recv_message(port->npe, &msg, "PORT_ERROR_READ")) { + pr_crit("HSS-%i: unable to read HSS status\n", port->id); + BUG(); + } + + return msg.data32; +} + +static void hss_start_hdlc(struct port *port) +{ + struct msg msg; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PKT_PIPE_FLOW_ENABLE; + msg.hss_port = port->id; + msg.data32 = 0; + hss_npe_send(port, &msg, "HSS_ENABLE_PKT_PIPE"); +} + +static void hss_stop_hdlc(struct port *port) +{ + struct msg msg; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = PKT_PIPE_FLOW_DISABLE; + msg.hss_port = port->id; + hss_npe_send(port, &msg, "HSS_DISABLE_PKT_PIPE"); + hss_get_status(port); /* make sure it's halted */ +} + +static int hss_load_firmware(struct port *port) +{ + struct msg msg; + int err; + + if (port->initialized) + return 0; + + if (!npe_running(port->npe) && + (err = npe_load_firmware(port->npe, npe_name(port->npe), + port->dev))) + return err; + + /* HDLC mode configuration */ + memset(&msg, 0, sizeof(msg)); + msg.cmd = PKT_NUM_PIPES_WRITE; + msg.hss_port = port->id; + msg.data8a = PKT_NUM_PIPES; + hss_npe_send(port, &msg, "HSS_SET_PKT_PIPES"); + + msg.cmd = PKT_PIPE_FIFO_SIZEW_WRITE; + msg.data8a = PKT_PIPE_FIFO_SIZEW; + hss_npe_send(port, &msg, "HSS_SET_PKT_FIFO"); + + msg.cmd = PKT_PIPE_MODE_WRITE; + msg.data8a = NPE_PKT_MODE_HDLC; + /* msg.data8b = inv_mask */ + /* msg.data8c = or_mask */ + hss_npe_send(port, &msg, "HSS_SET_PKT_MODE"); + + msg.cmd = PKT_PIPE_RX_SIZE_WRITE; + msg.data16a = HDLC_MAX_MRU; /* including CRC */ + hss_npe_send(port, &msg, "HSS_SET_PKT_RX_SIZE"); + + msg.cmd = PKT_PIPE_IDLE_PATTERN_WRITE; + msg.data32 = 0x7F7F7F7F; /* ??? FIXME */ + hss_npe_send(port, &msg, "HSS_SET_PKT_IDLE"); + + port->initialized = 1; + return 0; +} + +/***************************************************************************** + * packetized (HDLC) operation + ****************************************************************************/ + +static inline void debug_pkt(struct net_device *dev, const char *func, + u8 *data, int len) +{ +#if DEBUG_PKT_BYTES + int i; + + printk(KERN_DEBUG "%s: %s(%i)", dev->name, func, len); + for (i = 0; i < len; i++) { + if (i >= DEBUG_PKT_BYTES) + break; + printk("%s%02X", !(i % 4) ? " " : "", data[i]); + } + printk("\n"); +#endif +} + + +static inline void debug_desc(u32 phys, struct desc *desc) +{ +#if DEBUG_DESC + printk(KERN_DEBUG "%X: %X %3X %3X %08X %X %X\n", + phys, desc->next, desc->buf_len, desc->pkt_len, + desc->data, desc->status, desc->error_count); +#endif +} + +static inline int queue_get_desc(unsigned int queue, struct port *port, + int is_tx) +{ + u32 phys, tab_phys, n_desc; + struct desc *tab; + + if (!(phys = qmgr_get_entry(queue))) + return -1; + + BUG_ON(phys & 0x1F); + tab_phys = is_tx ? tx_desc_phys(port, 0) : rx_desc_phys(port, 0); + tab = is_tx ? tx_desc_ptr(port, 0) : rx_desc_ptr(port, 0); + n_desc = (phys - tab_phys) / sizeof(struct desc); + BUG_ON(n_desc >= (is_tx ? TX_DESCS : RX_DESCS)); + debug_desc(phys, &tab[n_desc]); + BUG_ON(tab[n_desc].next); + return n_desc; +} + +static inline void queue_put_desc(unsigned int queue, u32 phys, + struct desc *desc) +{ + debug_desc(phys, desc); + BUG_ON(phys & 0x1F); + qmgr_put_entry(queue, phys); + /* Don't check for queue overflow here, we've allocated sufficient + length and queues >= 32 don't support this check anyway. */ +} + + +static inline void dma_unmap_tx(struct port *port, struct desc *desc) +{ +#ifdef __ARMEB__ + dma_unmap_single(&port->netdev->dev, desc->data, + desc->buf_len, DMA_TO_DEVICE); +#else + dma_unmap_single(&port->netdev->dev, desc->data & ~3, + ALIGN((desc->data & 3) + desc->buf_len, 4), + DMA_TO_DEVICE); +#endif +} + + +static void hss_hdlc_set_carrier(void *pdev, int carrier) +{ + struct net_device *netdev = pdev; + struct port *port = dev_to_port(netdev); + unsigned long flags; + + spin_lock_irqsave(&npe_lock, flags); + port->carrier = carrier; + if (!port->loopback) { + if (carrier) + netif_carrier_on(netdev); + else + netif_carrier_off(netdev); + } + spin_unlock_irqrestore(&npe_lock, flags); +} + +static void hss_hdlc_rx_irq(void *pdev) +{ + struct net_device *dev = pdev; + struct port *port = dev_to_port(dev); + +#if DEBUG_RX + printk(KERN_DEBUG "%s: hss_hdlc_rx_irq\n", dev->name); +#endif + qmgr_disable_irq(queue_ids[port->id].rx); + napi_schedule(&port->napi); +} + +static int hss_hdlc_poll(struct napi_struct *napi, int budget) +{ + struct port *port = container_of(napi, struct port, napi); + struct net_device *dev = port->netdev; + unsigned int rxq = queue_ids[port->id].rx; + unsigned int rxfreeq = queue_ids[port->id].rxfree; + int received = 0; + +#if DEBUG_RX + printk(KERN_DEBUG "%s: hss_hdlc_poll\n", dev->name); +#endif + + while (received < budget) { + struct sk_buff *skb; + struct desc *desc; + int n; +#ifdef __ARMEB__ + struct sk_buff *temp; + u32 phys; +#endif + + if ((n = queue_get_desc(rxq, port, 0)) < 0) { +#if DEBUG_RX + printk(KERN_DEBUG "%s: hss_hdlc_poll" + " napi_complete\n", dev->name); +#endif + napi_complete(napi); + qmgr_enable_irq(rxq); + if (!qmgr_stat_empty(rxq) && + napi_reschedule(napi)) { +#if DEBUG_RX + printk(KERN_DEBUG "%s: hss_hdlc_poll" + " napi_reschedule succeeded\n", + dev->name); +#endif + qmgr_disable_irq(rxq); + continue; + } +#if DEBUG_RX + printk(KERN_DEBUG "%s: hss_hdlc_poll all done\n", + dev->name); +#endif + return received; /* all work done */ + } + + desc = rx_desc_ptr(port, n); +#if 0 /* FIXME - error_count counts modulo 256, perhaps we should use it */ + if (desc->error_count) + printk(KERN_DEBUG "%s: hss_hdlc_poll status 0x%02X" + " errors %u\n", dev->name, desc->status, + desc->error_count); +#endif + skb = NULL; + switch (desc->status) { + case 0: +#ifdef __ARMEB__ + if ((skb = netdev_alloc_skb(dev, RX_SIZE)) != NULL) { + phys = dma_map_single(&dev->dev, skb->data, + RX_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(&dev->dev, phys)) { + dev_kfree_skb(skb); + skb = NULL; + } + } +#else + skb = netdev_alloc_skb(dev, desc->pkt_len); +#endif + if (!skb) + dev->stats.rx_dropped++; + break; + case ERR_HDLC_ALIGN: + case ERR_HDLC_ABORT: + dev->stats.rx_frame_errors++; + dev->stats.rx_errors++; + break; + case ERR_HDLC_FCS: + dev->stats.rx_crc_errors++; + dev->stats.rx_errors++; + break; + case ERR_HDLC_TOO_LONG: + dev->stats.rx_length_errors++; + dev->stats.rx_errors++; + break; + default: /* FIXME - remove printk */ + netdev_err(dev, "hss_hdlc_poll: status 0x%02X errors %u\n", + desc->status, desc->error_count); + dev->stats.rx_errors++; + } + + if (!skb) { + /* put the desc back on RX-ready queue */ + desc->buf_len = RX_SIZE; + desc->pkt_len = desc->status = 0; + queue_put_desc(rxfreeq, rx_desc_phys(port, n), desc); + continue; + } + + /* process received frame */ +#ifdef __ARMEB__ + temp = skb; + skb = port->rx_buff_tab[n]; + dma_unmap_single(&dev->dev, desc->data, + RX_SIZE, DMA_FROM_DEVICE); +#else + dma_sync_single_for_cpu(&dev->dev, desc->data, + RX_SIZE, DMA_FROM_DEVICE); + memcpy_swab32((u32 *)skb->data, (u32 *)port->rx_buff_tab[n], + ALIGN(desc->pkt_len, 4) / 4); +#endif + skb_put(skb, desc->pkt_len); + + debug_pkt(dev, "hss_hdlc_poll", skb->data, skb->len); + + skb->protocol = hdlc_type_trans(skb, dev); + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + netif_receive_skb(skb); + + /* put the new buffer on RX-free queue */ +#ifdef __ARMEB__ + port->rx_buff_tab[n] = temp; + desc->data = phys; +#endif + desc->buf_len = RX_SIZE; + desc->pkt_len = 0; + queue_put_desc(rxfreeq, rx_desc_phys(port, n), desc); + received++; + } +#if DEBUG_RX + printk(KERN_DEBUG "hss_hdlc_poll: end, not all work done\n"); +#endif + return received; /* not all work done */ +} + + +static void hss_hdlc_txdone_irq(void *pdev) +{ + struct net_device *dev = pdev; + struct port *port = dev_to_port(dev); + int n_desc; + +#if DEBUG_TX + printk(KERN_DEBUG DRV_NAME ": hss_hdlc_txdone_irq\n"); +#endif + while ((n_desc = queue_get_desc(queue_ids[port->id].txdone, + port, 1)) >= 0) { + struct desc *desc; + int start; + + desc = tx_desc_ptr(port, n_desc); + + dev->stats.tx_packets++; + dev->stats.tx_bytes += desc->pkt_len; + + dma_unmap_tx(port, desc); +#if DEBUG_TX + printk(KERN_DEBUG "%s: hss_hdlc_txdone_irq free %p\n", + dev->name, port->tx_buff_tab[n_desc]); +#endif + free_buffer_irq(port->tx_buff_tab[n_desc]); + port->tx_buff_tab[n_desc] = NULL; + + start = qmgr_stat_below_low_watermark(port->plat->txreadyq); + queue_put_desc(port->plat->txreadyq, + tx_desc_phys(port, n_desc), desc); + if (start) { /* TX-ready queue was empty */ +#if DEBUG_TX + printk(KERN_DEBUG "%s: hss_hdlc_txdone_irq xmit" + " ready\n", dev->name); +#endif + netif_wake_queue(dev); + } + } +} + +static int hss_hdlc_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct port *port = dev_to_port(dev); + unsigned int txreadyq = port->plat->txreadyq; + int len, offset, bytes, n; + void *mem; + u32 phys; + struct desc *desc; + +#if DEBUG_TX + printk(KERN_DEBUG "%s: hss_hdlc_xmit\n", dev->name); +#endif + + if (unlikely(skb->len > HDLC_MAX_MRU)) { + dev_kfree_skb(skb); + dev->stats.tx_errors++; + return NETDEV_TX_OK; + } + + debug_pkt(dev, "hss_hdlc_xmit", skb->data, skb->len); + + len = skb->len; +#ifdef __ARMEB__ + offset = 0; /* no need to keep alignment */ + bytes = len; + mem = skb->data; +#else + offset = (int)skb->data & 3; /* keep 32-bit alignment */ + bytes = ALIGN(offset + len, 4); + if (!(mem = kmalloc(bytes, GFP_ATOMIC))) { + dev_kfree_skb(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + memcpy_swab32(mem, (u32 *)((uintptr_t)skb->data & ~3), bytes / 4); + dev_kfree_skb(skb); +#endif + + phys = dma_map_single(&dev->dev, mem, bytes, DMA_TO_DEVICE); + if (dma_mapping_error(&dev->dev, phys)) { +#ifdef __ARMEB__ + dev_kfree_skb(skb); +#else + kfree(mem); +#endif + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + n = queue_get_desc(txreadyq, port, 1); + BUG_ON(n < 0); + desc = tx_desc_ptr(port, n); + +#ifdef __ARMEB__ + port->tx_buff_tab[n] = skb; +#else + port->tx_buff_tab[n] = mem; +#endif + desc->data = phys + offset; + desc->buf_len = desc->pkt_len = len; + + wmb(); + queue_put_desc(queue_ids[port->id].tx, tx_desc_phys(port, n), desc); + + if (qmgr_stat_below_low_watermark(txreadyq)) { /* empty */ +#if DEBUG_TX + printk(KERN_DEBUG "%s: hss_hdlc_xmit queue full\n", dev->name); +#endif + netif_stop_queue(dev); + /* we could miss TX ready interrupt */ + if (!qmgr_stat_below_low_watermark(txreadyq)) { +#if DEBUG_TX + printk(KERN_DEBUG "%s: hss_hdlc_xmit ready again\n", + dev->name); +#endif + netif_wake_queue(dev); + } + } + +#if DEBUG_TX + printk(KERN_DEBUG "%s: hss_hdlc_xmit end\n", dev->name); +#endif + return NETDEV_TX_OK; +} + + +static int request_hdlc_queues(struct port *port) +{ + int err; + + err = qmgr_request_queue(queue_ids[port->id].rxfree, RX_DESCS, 0, 0, + "%s:RX-free", port->netdev->name); + if (err) + return err; + + err = qmgr_request_queue(queue_ids[port->id].rx, RX_DESCS, 0, 0, + "%s:RX", port->netdev->name); + if (err) + goto rel_rxfree; + + err = qmgr_request_queue(queue_ids[port->id].tx, TX_DESCS, 0, 0, + "%s:TX", port->netdev->name); + if (err) + goto rel_rx; + + err = qmgr_request_queue(port->plat->txreadyq, TX_DESCS, 0, 0, + "%s:TX-ready", port->netdev->name); + if (err) + goto rel_tx; + + err = qmgr_request_queue(queue_ids[port->id].txdone, TX_DESCS, 0, 0, + "%s:TX-done", port->netdev->name); + if (err) + goto rel_txready; + return 0; + +rel_txready: + qmgr_release_queue(port->plat->txreadyq); +rel_tx: + qmgr_release_queue(queue_ids[port->id].tx); +rel_rx: + qmgr_release_queue(queue_ids[port->id].rx); +rel_rxfree: + qmgr_release_queue(queue_ids[port->id].rxfree); + printk(KERN_DEBUG "%s: unable to request hardware queues\n", + port->netdev->name); + return err; +} + +static void release_hdlc_queues(struct port *port) +{ + qmgr_release_queue(queue_ids[port->id].rxfree); + qmgr_release_queue(queue_ids[port->id].rx); + qmgr_release_queue(queue_ids[port->id].txdone); + qmgr_release_queue(queue_ids[port->id].tx); + qmgr_release_queue(port->plat->txreadyq); +} + +static int init_hdlc_queues(struct port *port) +{ + int i; + + if (!ports_open) { + dma_pool = dma_pool_create(DRV_NAME, &port->netdev->dev, + POOL_ALLOC_SIZE, 32, 0); + if (!dma_pool) + return -ENOMEM; + } + + if (!(port->desc_tab = dma_pool_alloc(dma_pool, GFP_KERNEL, + &port->desc_tab_phys))) + return -ENOMEM; + memset(port->desc_tab, 0, POOL_ALLOC_SIZE); + memset(port->rx_buff_tab, 0, sizeof(port->rx_buff_tab)); /* tables */ + memset(port->tx_buff_tab, 0, sizeof(port->tx_buff_tab)); + + /* Setup RX buffers */ + for (i = 0; i < RX_DESCS; i++) { + struct desc *desc = rx_desc_ptr(port, i); + buffer_t *buff; + void *data; +#ifdef __ARMEB__ + if (!(buff = netdev_alloc_skb(port->netdev, RX_SIZE))) + return -ENOMEM; + data = buff->data; +#else + if (!(buff = kmalloc(RX_SIZE, GFP_KERNEL))) + return -ENOMEM; + data = buff; +#endif + desc->buf_len = RX_SIZE; + desc->data = dma_map_single(&port->netdev->dev, data, + RX_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(&port->netdev->dev, desc->data)) { + free_buffer(buff); + return -EIO; + } + port->rx_buff_tab[i] = buff; + } + + return 0; +} + +static void destroy_hdlc_queues(struct port *port) +{ + int i; + + if (port->desc_tab) { + for (i = 0; i < RX_DESCS; i++) { + struct desc *desc = rx_desc_ptr(port, i); + buffer_t *buff = port->rx_buff_tab[i]; + if (buff) { + dma_unmap_single(&port->netdev->dev, + desc->data, RX_SIZE, + DMA_FROM_DEVICE); + free_buffer(buff); + } + } + for (i = 0; i < TX_DESCS; i++) { + struct desc *desc = tx_desc_ptr(port, i); + buffer_t *buff = port->tx_buff_tab[i]; + if (buff) { + dma_unmap_tx(port, desc); + free_buffer(buff); + } + } + dma_pool_free(dma_pool, port->desc_tab, port->desc_tab_phys); + port->desc_tab = NULL; + } + + if (!ports_open && dma_pool) { + dma_pool_destroy(dma_pool); + dma_pool = NULL; + } +} + +static int hss_hdlc_open(struct net_device *dev) +{ + struct port *port = dev_to_port(dev); + unsigned long flags; + int i, err = 0; + + if ((err = hdlc_open(dev))) + return err; + + if ((err = hss_load_firmware(port))) + goto err_hdlc_close; + + if ((err = request_hdlc_queues(port))) + goto err_hdlc_close; + + if ((err = init_hdlc_queues(port))) + goto err_destroy_queues; + + spin_lock_irqsave(&npe_lock, flags); + if (port->plat->open) + if ((err = port->plat->open(port->id, dev, + hss_hdlc_set_carrier))) + goto err_unlock; + spin_unlock_irqrestore(&npe_lock, flags); + + /* Populate queues with buffers, no failure after this point */ + for (i = 0; i < TX_DESCS; i++) + queue_put_desc(port->plat->txreadyq, + tx_desc_phys(port, i), tx_desc_ptr(port, i)); + + for (i = 0; i < RX_DESCS; i++) + queue_put_desc(queue_ids[port->id].rxfree, + rx_desc_phys(port, i), rx_desc_ptr(port, i)); + + napi_enable(&port->napi); + netif_start_queue(dev); + + qmgr_set_irq(queue_ids[port->id].rx, QUEUE_IRQ_SRC_NOT_EMPTY, + hss_hdlc_rx_irq, dev); + + qmgr_set_irq(queue_ids[port->id].txdone, QUEUE_IRQ_SRC_NOT_EMPTY, + hss_hdlc_txdone_irq, dev); + qmgr_enable_irq(queue_ids[port->id].txdone); + + ports_open++; + + hss_set_hdlc_cfg(port); + hss_config(port); + + hss_start_hdlc(port); + + /* we may already have RX data, enables IRQ */ + napi_schedule(&port->napi); + return 0; + +err_unlock: + spin_unlock_irqrestore(&npe_lock, flags); +err_destroy_queues: + destroy_hdlc_queues(port); + release_hdlc_queues(port); +err_hdlc_close: + hdlc_close(dev); + return err; +} + +static int hss_hdlc_close(struct net_device *dev) +{ + struct port *port = dev_to_port(dev); + unsigned long flags; + int i, buffs = RX_DESCS; /* allocated RX buffers */ + + spin_lock_irqsave(&npe_lock, flags); + ports_open--; + qmgr_disable_irq(queue_ids[port->id].rx); + netif_stop_queue(dev); + napi_disable(&port->napi); + + hss_stop_hdlc(port); + + while (queue_get_desc(queue_ids[port->id].rxfree, port, 0) >= 0) + buffs--; + while (queue_get_desc(queue_ids[port->id].rx, port, 0) >= 0) + buffs--; + + if (buffs) + netdev_crit(dev, "unable to drain RX queue, %i buffer(s) left in NPE\n", + buffs); + + buffs = TX_DESCS; + while (queue_get_desc(queue_ids[port->id].tx, port, 1) >= 0) + buffs--; /* cancel TX */ + + i = 0; + do { + while (queue_get_desc(port->plat->txreadyq, port, 1) >= 0) + buffs--; + if (!buffs) + break; + } while (++i < MAX_CLOSE_WAIT); + + if (buffs) + netdev_crit(dev, "unable to drain TX queue, %i buffer(s) left in NPE\n", + buffs); +#if DEBUG_CLOSE + if (!buffs) + printk(KERN_DEBUG "Draining TX queues took %i cycles\n", i); +#endif + qmgr_disable_irq(queue_ids[port->id].txdone); + + if (port->plat->close) + port->plat->close(port->id, dev); + spin_unlock_irqrestore(&npe_lock, flags); + + destroy_hdlc_queues(port); + release_hdlc_queues(port); + hdlc_close(dev); + return 0; +} + + +static int hss_hdlc_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + struct port *port = dev_to_port(dev); + + if (encoding != ENCODING_NRZ) + return -EINVAL; + + switch(parity) { + case PARITY_CRC16_PR1_CCITT: + port->hdlc_cfg = 0; + return 0; + + case PARITY_CRC32_PR1_CCITT: + port->hdlc_cfg = PKT_HDLC_CRC_32; + return 0; + + default: + return -EINVAL; + } +} + +static u32 check_clock(u32 rate, u32 a, u32 b, u32 c, + u32 *best, u32 *best_diff, u32 *reg) +{ + /* a is 10-bit, b is 10-bit, c is 12-bit */ + u64 new_rate; + u32 new_diff; + + new_rate = ixp4xx_timer_freq * (u64)(c + 1); + do_div(new_rate, a * (c + 1) + b + 1); + new_diff = abs((u32)new_rate - rate); + + if (new_diff < *best_diff) { + *best = new_rate; + *best_diff = new_diff; + *reg = (a << 22) | (b << 12) | c; + } + return new_diff; +} + +static void find_best_clock(u32 rate, u32 *best, u32 *reg) +{ + u32 a, b, diff = 0xFFFFFFFF; + + a = ixp4xx_timer_freq / rate; + + if (a > 0x3FF) { /* 10-bit value - we can go as slow as ca. 65 kb/s */ + check_clock(rate, 0x3FF, 1, 1, best, &diff, reg); + return; + } + if (a == 0) { /* > 66.666 MHz */ + a = 1; /* minimum divider is 1 (a = 0, b = 1, c = 1) */ + rate = ixp4xx_timer_freq; + } + + if (rate * a == ixp4xx_timer_freq) { /* don't divide by 0 later */ + check_clock(rate, a - 1, 1, 1, best, &diff, reg); + return; + } + + for (b = 0; b < 0x400; b++) { + u64 c = (b + 1) * (u64)rate; + do_div(c, ixp4xx_timer_freq - rate * a); + c--; + if (c >= 0xFFF) { /* 12-bit - no need to check more 'b's */ + if (b == 0 && /* also try a bit higher rate */ + !check_clock(rate, a - 1, 1, 1, best, &diff, reg)) + return; + check_clock(rate, a, b, 0xFFF, best, &diff, reg); + return; + } + if (!check_clock(rate, a, b, c, best, &diff, reg)) + return; + if (!check_clock(rate, a, b, c + 1, best, &diff, reg)) + return; + } +} + +static int hss_hdlc_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; + struct port *port = dev_to_port(dev); + unsigned long flags; + int clk; + + 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; + } + memset(&new_line, 0, sizeof(new_line)); + new_line.clock_type = port->clock_type; + new_line.clock_rate = port->clock_rate; + new_line.loopback = port->loopback; + if (copy_to_user(line, &new_line, size)) + return -EFAULT; + return 0; + + case IF_IFACE_SYNC_SERIAL: + case IF_IFACE_V35: + if(!capable(CAP_NET_ADMIN)) + return -EPERM; + if (copy_from_user(&new_line, line, size)) + return -EFAULT; + + clk = new_line.clock_type; + if (port->plat->set_clock) + clk = port->plat->set_clock(port->id, clk); + + if (clk != CLOCK_EXT && clk != CLOCK_INT) + return -EINVAL; /* No such clock setting */ + + if (new_line.loopback != 0 && new_line.loopback != 1) + return -EINVAL; + + port->clock_type = clk; /* Update settings */ + if (clk == CLOCK_INT) + find_best_clock(new_line.clock_rate, &port->clock_rate, + &port->clock_reg); + else { + port->clock_rate = 0; + port->clock_reg = CLK42X_SPEED_2048KHZ; + } + port->loopback = new_line.loopback; + + spin_lock_irqsave(&npe_lock, flags); + + if (dev->flags & IFF_UP) + hss_config(port); + + if (port->loopback || port->carrier) + netif_carrier_on(port->netdev); + else + netif_carrier_off(port->netdev); + spin_unlock_irqrestore(&npe_lock, flags); + + return 0; + + default: + return hdlc_ioctl(dev, ifr, cmd); + } +} + +/***************************************************************************** + * initialization + ****************************************************************************/ + +static const struct net_device_ops hss_hdlc_ops = { + .ndo_open = hss_hdlc_open, + .ndo_stop = hss_hdlc_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = hss_hdlc_ioctl, +}; + +static int hss_init_one(struct platform_device *pdev) +{ + struct port *port; + struct net_device *dev; + hdlc_device *hdlc; + int err; + + if ((port = kzalloc(sizeof(*port), GFP_KERNEL)) == NULL) + return -ENOMEM; + + if ((port->npe = npe_request(0)) == NULL) { + err = -ENODEV; + goto err_free; + } + + if ((port->netdev = dev = alloc_hdlcdev(port)) == NULL) { + err = -ENOMEM; + goto err_plat; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + hdlc = dev_to_hdlc(dev); + hdlc->attach = hss_hdlc_attach; + hdlc->xmit = hss_hdlc_xmit; + dev->netdev_ops = &hss_hdlc_ops; + dev->tx_queue_len = 100; + port->clock_type = CLOCK_EXT; + port->clock_rate = 0; + port->clock_reg = CLK42X_SPEED_2048KHZ; + port->id = pdev->id; + port->dev = &pdev->dev; + port->plat = pdev->dev.platform_data; + netif_napi_add(dev, &port->napi, hss_hdlc_poll, NAPI_WEIGHT); + + if ((err = register_hdlc_device(dev))) + goto err_free_netdev; + + platform_set_drvdata(pdev, port); + + netdev_info(dev, "initialized\n"); + return 0; + +err_free_netdev: + free_netdev(dev); +err_plat: + npe_release(port->npe); +err_free: + kfree(port); + return err; +} + +static int hss_remove_one(struct platform_device *pdev) +{ + struct port *port = platform_get_drvdata(pdev); + + unregister_hdlc_device(port->netdev); + free_netdev(port->netdev); + npe_release(port->npe); + kfree(port); + return 0; +} + +static struct platform_driver ixp4xx_hss_driver = { + .driver.name = DRV_NAME, + .probe = hss_init_one, + .remove = hss_remove_one, +}; + +static int __init hss_init_module(void) +{ + if ((ixp4xx_read_feature_bits() & + (IXP4XX_FEATURE_HDLC | IXP4XX_FEATURE_HSS)) != + (IXP4XX_FEATURE_HDLC | IXP4XX_FEATURE_HSS)) + return -ENODEV; + + spin_lock_init(&npe_lock); + + return platform_driver_register(&ixp4xx_hss_driver); +} + +static void __exit hss_cleanup_module(void) +{ + platform_driver_unregister(&ixp4xx_hss_driver); +} + +MODULE_AUTHOR("Krzysztof Halasa"); +MODULE_DESCRIPTION("Intel IXP4xx HSS driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ixp4xx_hss"); +module_init(hss_init_module); +module_exit(hss_cleanup_module); diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c new file mode 100644 index 000000000..3ec922bed --- /dev/null +++ b/drivers/net/wan/lapbether.c @@ -0,0 +1,482 @@ +/* + * "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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/slab.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 <linux/uaccess.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/lapb.h> +#include <linux/init.h> + +#include <net/x25device.h> + +static const u8 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#) */ + bool up; + spinlock_t up_lock; /* Protects "up" */ +}; + +static LIST_HEAD(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, struct net_device *orig_dev) +{ + int len, err; + struct lapbethdev *lapbeth; + + if (dev_net(dev) != &init_net) + goto drop; + + 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_rcu; + spin_lock_bh(&lapbeth->up_lock); + if (!lapbeth->up) + goto drop_unlock; + + len = skb->data[0] + skb->data[1] * 256; + dev->stats.rx_packets++; + dev->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: + spin_unlock_bh(&lapbeth->up_lock); + rcu_read_unlock(); + return 0; +drop_unlock: + kfree_skb(skb); + goto out; +drop_unlock_rcu: + rcu_read_unlock(); +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 = X25_IFACE_DATA; + + skb->protocol = x25_type_trans(skb, dev); + return netif_rx(skb); +} + +/* + * Send a LAPB frame via an ethernet interface + */ +static netdev_tx_t lapbeth_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct lapbethdev *lapbeth = netdev_priv(dev); + int err; + + spin_lock_bh(&lapbeth->up_lock); + if (!lapbeth->up) + goto drop; + + /* There should be a pseudo header of 1 byte added by upper layers. + * Check to make sure it is there before reading it. + */ + if (skb->len < 1) + goto drop; + + switch (skb->data[0]) { + case X25_IFACE_DATA: + break; + case X25_IFACE_CONNECT: + if ((err = lapb_connect_request(dev)) != LAPB_OK) + pr_err("lapb_connect_request error: %d\n", err); + goto drop; + case X25_IFACE_DISCONNECT: + if ((err = lapb_disconnect_request(dev)) != LAPB_OK) + pr_err("lapb_disconnect_request err: %d\n", err); + /* Fall thru */ + default: + goto drop; + } + + skb_pull(skb, 1); + + if ((err = lapb_data_request(dev, skb)) != LAPB_OK) { + pr_err("lapb_data_request error - %d\n", err); + goto drop; + } +out: + spin_unlock_bh(&lapbeth->up_lock); + return NETDEV_TX_OK; +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; + + ptr = skb_push(skb, 2); + + *ptr++ = size % 256; + *ptr++ = size / 256; + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += size; + + skb->dev = dev = lapbeth->ethdev; + + skb->protocol = htons(ETH_P_DEC); + + skb_reset_network_header(skb); + + 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) { + pr_err("out of memory\n"); + return; + } + + ptr = skb_put(skb, 1); + *ptr = X25_IFACE_CONNECT; + + skb->protocol = x25_type_trans(skb, dev); + 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) { + pr_err("out of memory\n"); + return; + } + + ptr = skb_put(skb, 1); + *ptr = X25_IFACE_DISCONNECT; + + skb->protocol = x25_type_trans(skb, dev); + netif_rx(skb); +} + +/* + * 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 const 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) +{ + struct lapbethdev *lapbeth = netdev_priv(dev); + int err; + + if ((err = lapb_register(dev, &lapbeth_callbacks)) != LAPB_OK) { + pr_err("lapb_register error: %d\n", err); + return -ENODEV; + } + + spin_lock_bh(&lapbeth->up_lock); + lapbeth->up = true; + spin_unlock_bh(&lapbeth->up_lock); + + return 0; +} + +static int lapbeth_close(struct net_device *dev) +{ + struct lapbethdev *lapbeth = netdev_priv(dev); + int err; + + spin_lock_bh(&lapbeth->up_lock); + lapbeth->up = false; + spin_unlock_bh(&lapbeth->up_lock); + + if ((err = lapb_unregister(dev)) != LAPB_OK) + pr_err("lapb_unregister error: %d\n", err); + + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static const struct net_device_ops lapbeth_netdev_ops = { + .ndo_open = lapbeth_open, + .ndo_stop = lapbeth_close, + .ndo_start_xmit = lapbeth_xmit, + .ndo_set_mac_address = lapbeth_set_mac_address, +}; + +static void lapbeth_setup(struct net_device *dev) +{ + dev->netdev_ops = &lapbeth_netdev_ops; + dev->needs_free_netdev = true; + dev->type = ARPHRD_X25; + dev->hard_header_len = 0; + dev->mtu = 1000; + dev->addr_len = 0; +} + +/* + * 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", NET_NAME_UNKNOWN, + lapbeth_setup); + if (!ndev) + goto out; + + /* When transmitting data: + * first this driver removes a pseudo header of 1 byte, + * then the lapb module prepends an LAPB header of at most 3 bytes, + * then this driver prepends a length field of 2 bytes, + * then the underlying Ethernet device prepends its own header. + */ + ndev->needed_headroom = -1 + 3 + 2 + dev->hard_header_len + + dev->needed_headroom; + ndev->needed_tailroom = dev->needed_tailroom; + + lapbeth = netdev_priv(ndev); + lapbeth->axdev = ndev; + + dev_hold(dev); + lapbeth->ethdev = dev; + + lapbeth->up = false; + spin_lock_init(&lapbeth->up_lock); + + 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); + 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 = netdev_notifier_info_to_dev(ptr); + + if (dev_net(dev) != &init_net) + return NOTIFY_DONE; + + 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 __read_mostly = { + .type = cpu_to_be16(ETH_P_DEC), + .func = lapbeth_rcv, +}; + +static struct notifier_block lapbeth_dev_notifier = { + .notifier_call = lapbeth_device_event, +}; + +static const char banner[] __initconst = + 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); + + dev_put(lapbeth->ethdev); + 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 000000000..609710d64 --- /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 + +ccflags-y := -I. $(DBGDEF) diff --git a/drivers/net/wan/lmc/lmc.h b/drivers/net/wan/lmc/lmc.h new file mode 100644 index 000000000..38961793a --- /dev/null +++ b/drivers/net/wan/lmc/lmc.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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, u32); +void lmc_led_off(lmc_softc_t * const, u32); +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, u32 bits); +void lmc_gpio_mkoutput(lmc_softc_t * const sc, u32 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(u32 EventNum, u32 arg2, u32 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 000000000..f999db788 --- /dev/null +++ b/drivers/net/wan/lmc/lmc_debug.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +#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 + */ +#ifdef DEBUG +#ifdef LMC_PACKET_LOG +void lmcConsoleLog(char *type, unsigned char *ucData, int iLen) +{ + 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 +u32 lmcEventLogIndex; +u32 lmcEventLogBuf[LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS]; + +void lmcEventLog(u32 EventNum, u32 arg2, u32 arg3) +{ + lmcEventLogBuf[lmcEventLogIndex++] = EventNum; + lmcEventLogBuf[lmcEventLogIndex++] = arg2; + lmcEventLogBuf[lmcEventLogIndex++] = arg3; + lmcEventLogBuf[lmcEventLogIndex++] = jiffies; + + lmcEventLogIndex &= (LMC_EVENTLOGSIZE * LMC_EVENTLOGARGS) - 1; +} +#endif /* DEBUG */ + +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 000000000..820adcae5 --- /dev/null +++ b/drivers/net/wan/lmc/lmc_debug.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 u32 lmcEventLogIndex; +extern u32 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(u32 EventNum, u32 arg2, u32 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 000000000..72fb113a4 --- /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 generic HDLC 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 000000000..937f56d0a --- /dev/null +++ b/drivers/net/wan/lmc/lmc_main.c @@ -0,0 +1,2107 @@ + /* + * Copyright (c) 1997-2000 LAN Media Corporation (LMC) + * All rights reserved. www.lanmedia.com + * Generic HDLC port Copyright (C) 2008 Krzysztof Halasa <khc@pm.waw.pl> + * + * 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 + * around. + * + * 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. + * + */ + +#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/hdlc.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 <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/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_PKT_BUF_SZ = 1542; + +static const 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 v2"); + + +static netdev_tx_t 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); +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(struct timer_list *t); +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 = dev_to_sc(dev); + lmc_ctl_t ctl; + int ret = -EOPNOTSUPP; + u16 regVal; + unsigned long flags; + + lmc_trace(dev, "lmc_ioctl in"); + + /* + * Most functions mess with the structure + * Disable interrupts while we do the polling + */ + + 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))) + ret = -EFAULT; + else + ret = 0; + break; + + case LMCIOCSINFO: /*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))) { + ret = -EFAULT; + break; + } + + spin_lock_irqsave(&sc->lmc_lock, flags); + 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; + } + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + ret = 0; + break; + + case LMCIOCIFTYPE: /*fold01*/ + { + u16 old_type = sc->if_type; + u16 new_type; + + if (!capable(CAP_NET_ADMIN)) { + ret = -EPERM; + break; + } + + if (copy_from_user(&new_type, ifr->ifr_data, sizeof(u16))) { + ret = -EFAULT; + break; + } + + + if (new_type == old_type) + { + ret = 0 ; + break; /* no change */ + } + + spin_lock_irqsave(&sc->lmc_lock, flags); + lmc_proto_close(sc); + + sc->if_type = new_type; + lmc_proto_attach(sc); + ret = lmc_proto_open(sc); + spin_unlock_irqrestore(&sc->lmc_lock, flags); + break; + } + + case LMCIOCGETXINFO: /*fold01*/ + spin_lock_irqsave(&sc->lmc_lock, flags); + 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); + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + sc->lmc_xinfo.Magic1 = 0xDEADBEEF; + + if (copy_to_user(ifr->ifr_data, &sc->lmc_xinfo, + sizeof(struct lmc_xinfo))) + ret = -EFAULT; + else + ret = 0; + + break; + + case LMCIOCGETLMCSTATS: + spin_lock_irqsave(&sc->lmc_lock, flags); + if (sc->lmc_cardtype == LMC_CARDTYPE_T1) { + lmc_mii_writereg(sc, 0, 17, T1FRAMER_FERR_LSB); + sc->extra_stats.framingBitErrorCount += + lmc_mii_readreg(sc, 0, 18) & 0xff; + lmc_mii_writereg(sc, 0, 17, T1FRAMER_FERR_MSB); + sc->extra_stats.framingBitErrorCount += + (lmc_mii_readreg(sc, 0, 18) & 0xff) << 8; + lmc_mii_writereg(sc, 0, 17, T1FRAMER_LCV_LSB); + sc->extra_stats.lineCodeViolationCount += + lmc_mii_readreg(sc, 0, 18) & 0xff; + lmc_mii_writereg(sc, 0, 17, T1FRAMER_LCV_MSB); + sc->extra_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->extra_stats.lossOfFrameCount += + (regVal & T1FRAMER_LOF_MASK) >> 4; + sc->extra_stats.changeOfFrameAlignmentCount += + (regVal & T1FRAMER_COFA_MASK) >> 2; + sc->extra_stats.severelyErroredFrameCount += + regVal & T1FRAMER_SEF_MASK; + } + spin_unlock_irqrestore(&sc->lmc_lock, flags); + if (copy_to_user(ifr->ifr_data, &sc->lmc_device->stats, + sizeof(sc->lmc_device->stats)) || + copy_to_user(ifr->ifr_data + sizeof(sc->lmc_device->stats), + &sc->extra_stats, sizeof(sc->extra_stats))) + ret = -EFAULT; + else + ret = 0; + break; + + case LMCIOCCLEARLMCSTATS: + if (!capable(CAP_NET_ADMIN)) { + ret = -EPERM; + break; + } + + spin_lock_irqsave(&sc->lmc_lock, flags); + memset(&sc->lmc_device->stats, 0, sizeof(sc->lmc_device->stats)); + memset(&sc->extra_stats, 0, sizeof(sc->extra_stats)); + sc->extra_stats.check = STATCHECK; + sc->extra_stats.version_size = (DRIVER_VERSION << 16) + + sizeof(sc->lmc_device->stats) + sizeof(sc->extra_stats); + sc->extra_stats.lmc_cardtype = sc->lmc_cardtype; + spin_unlock_irqrestore(&sc->lmc_lock, flags); + 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))) { + ret = -EFAULT; + break; + } + spin_lock_irqsave(&sc->lmc_lock, flags); + sc->lmc_media->set_circuit_type(sc, ctl.circuit_type); + sc->ictl.circuit_type = ctl.circuit_type; + spin_unlock_irqrestore(&sc->lmc_lock, flags); + ret = 0; + + break; + + case LMCIOCRESET: /*fold01*/ + if (!capable(CAP_NET_ADMIN)){ + ret = -EPERM; + break; + } + + spin_lock_irqsave(&sc->lmc_lock, flags); + /* 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)); + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + ret = 0; + break; + +#ifdef DEBUG + case LMCIOCDUMPEVENTLOG: + if (copy_to_user(ifr->ifr_data, &lmcEventLogIndex, sizeof(u32))) { + ret = -EFAULT; + break; + } + if (copy_to_user(ifr->ifr_data + sizeof(u32), lmcEventLogBuf, + sizeof(lmcEventLogBuf))) + ret = -EFAULT; + else + 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))) { + ret = -EFAULT; + break; + } + switch(xc.command){ + case lmc_xilinx_reset: /*fold02*/ + { + u16 mii; + spin_lock_irqsave(&sc->lmc_lock, flags); + 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); + } + } + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + + + ret = 0x0; + + } + + break; + case lmc_xilinx_load_prom: /*fold02*/ + { + u16 mii; + int timeout = 500000; + spin_lock_irqsave(&sc->lmc_lock, flags); + 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)) + cpu_relax(); + + + /* + * stop driving Xilinx-related signals + */ + lmc_gpio_mkinput(sc, 0xff); + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + ret = 0x0; + + + break; + + } + + case lmc_xilinx_load: /*fold02*/ + { + char *data; + int pos; + int timeout = 500000; + + if (!xc.data) { + ret = -EINVAL; + break; + } + + data = memdup_user(xc.data, xc.len); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + break; + } + + printk("%s: Starting load of data Len: %d at 0x%p == 0x%p\n", dev->name, xc.len, xc.data, data); + + spin_lock_irqsave(&sc->lmc_lock, flags); + 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)) + cpu_relax(); + + 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); + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + 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; + } + + lmc_trace(dev, "lmc_ioctl out"); + + return ret; +} + + +/* the watchdog process that cruises around */ +static void lmc_watchdog(struct timer_list *t) /*fold00*/ +{ + lmc_softc_t *sc = from_timer(sc, t, timer); + struct net_device *dev = sc->lmc_device; + int link_status; + u32 ticks; + unsigned long flags; + + lmc_trace(dev, "lmc_watchdog in"); + + spin_lock_irqsave(&sc->lmc_lock, flags); + + if(sc->check != 0xBEAFCAFE){ + printk("LMC: Corrupt net_device struct, 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->lmc_device->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->lmc_device->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->extra_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->lmc_device->stats.tx_packets; + } else { + sc->tx_TimeoutInd = 0; + sc->lastlmc_taint_tx = sc->lmc_taint_tx; + sc->lasttx_packets = sc->lmc_device->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 */ + netif_carrier_off(dev); + } + + /* + * 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??? */ + + netif_carrier_on(dev); + } + + /* 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 int lmc_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + if (encoding == ENCODING_NRZ && parity == PARITY_CRC16_PR1_CCITT) + return 0; + return -EINVAL; +} + +static const struct net_device_ops lmc_ops = { + .ndo_open = lmc_open, + .ndo_stop = lmc_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = lmc_ioctl, + .ndo_tx_timeout = lmc_driver_timeout, + .ndo_get_stats = lmc_get_stats, +}; + +static int lmc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + lmc_softc_t *sc; + struct net_device *dev; + u16 subdevice; + u16 AdapModelNum; + int err; + static int cards_found; + + /* lmc_trace(dev, "lmc_init_one in"); */ + + err = pcim_enable_device(pdev); + if (err) { + printk(KERN_ERR "lmc: pci enable failed: %d\n", err); + return err; + } + + err = pci_request_regions(pdev, "lmc"); + if (err) { + printk(KERN_ERR "lmc: pci_request_region failed\n"); + return err; + } + + /* + * Allocate our own device structure + */ + sc = devm_kzalloc(&pdev->dev, sizeof(lmc_softc_t), GFP_KERNEL); + if (!sc) + return -ENOMEM; + + dev = alloc_hdlcdev(sc); + if (!dev) { + printk(KERN_ERR "lmc:alloc_netdev for device failed\n"); + return -ENOMEM; + } + + + dev->type = ARPHRD_HDLC; + dev_to_hdlc(dev)->xmit = lmc_start_xmit; + dev_to_hdlc(dev)->attach = lmc_attach; + dev->netdev_ops = &lmc_ops; + dev->watchdog_timeo = HZ; /* 1 second */ + dev->tx_queue_len = 100; + sc->lmc_device = dev; + sc->name = dev->name; + sc->if_type = LMC_PPP; + sc->check = 0xBEAFCAFE; + dev->base_addr = pci_resource_start(pdev, 0); + dev->irq = pdev->irq; + pci_set_drvdata(pdev, 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_attach(sc); + + /* Init the spin lock so can call it latter */ + + spin_lock_init(&sc->lmc_lock); + pci_set_master(pdev); + + printk(KERN_INFO "%s: detected at %lx, irq %d\n", dev->name, + dev->base_addr, dev->irq); + + err = register_hdlc_device(dev); + if (err) { + printk(KERN_ERR "%s: register_netdev failed.\n", dev->name); + free_netdev(dev); + return err; + } + + 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(KERN_INFO "%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(KERN_INFO "%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(KERN_INFO "%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(KERN_INFO "%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 UNKNOWN CARD!\n", dev->name); + unregister_hdlc_device(dev); + return -EIO; + 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 || /* detect LMC1200 */ + subdevice != PCI_DEVICE_ID_LMC_T1) && + (AdapModelNum != LMC_ADAP_SSI || /* detect LMC1000 */ + subdevice != PCI_DEVICE_ID_LMC_SSI) && + (AdapModelNum != LMC_ADAP_DS3 || /* detect LMC5245 */ + subdevice != PCI_DEVICE_ID_LMC_DS3) && + (AdapModelNum != LMC_ADAP_HSSI || /* detect LMC5200 */ + subdevice != PCI_DEVICE_ID_LMC_HSSI)) + printk(KERN_WARNING "%s: Model number (%d) miscompare for PCI" + " Subsystem ID = 0x%04x\n", + dev->name, AdapModelNum, subdevice); + + /* + * reset clock + */ + LMC_CSR_WRITE (sc, csr_gp_timer, 0xFFFFFFFFUL); + + sc->board_idx = cards_found++; + sc->extra_stats.check = STATCHECK; + sc->extra_stats.version_size = (DRIVER_VERSION << 16) + + sizeof(sc->lmc_device->stats) + sizeof(sc->extra_stats); + sc->extra_stats.lmc_cardtype = sc->lmc_cardtype; + + sc->lmc_ok = 0; + sc->last_link_status = 0; + + lmc_trace(dev, "lmc_init_one out"); + return 0; +} + +/* + * Called from pci when removing module. + */ +static void lmc_remove_one(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + + if (dev) { + printk(KERN_DEBUG "%s: removing...\n", dev->name); + unregister_hdlc_device(dev); + free_netdev(dev); + } +} + +/* After this is called, packets can be sent. + * Does not initialize the addresses + */ +static int lmc_open(struct net_device *dev) +{ + lmc_softc_t *sc = dev_to_sc(dev); + int err; + + 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, IRQF_SHARED, 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; */ + + if ((err = lmc_proto_open(sc)) != 0) + return err; + + netif_start_queue(dev); + sc->extra_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. + */ + timer_setup(&sc->timer, lmc_watchdog, 0); + sc->timer.expires = jiffies + HZ; + 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 = dev_to_sc(dev); + + lmc_trace(dev, "lmc_running_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); + + netif_wake_queue(dev); + + sc->lmc_txfull = 0; + sc->extra_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) +{ + /* not calling release_region() as we should */ + lmc_softc_t *sc = dev_to_sc(dev); + + lmc_trace(dev, "lmc_close in"); + + 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_to_sc(dev); + 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->extra_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); + + sc->lmc_device->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->extra_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) /*fold00*/ +{ + struct net_device *dev = (struct net_device *) dev_instance; + lmc_softc_t *sc = dev_to_sc(dev); + 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"); + + 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->extra_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->lmc_device->stats.tx_errors++; + if (stat & 0x4104) + sc->lmc_device->stats.tx_aborted_errors++; + if (stat & 0x0C00) + sc->lmc_device->stats.tx_carrier_errors++; + if (stat & 0x0200) + sc->lmc_device->stats.tx_window_errors++; + if (stat & 0x0002) + sc->lmc_device->stats.tx_fifo_errors++; + } else { + sc->lmc_device->stats.tx_bytes += sc->lmc_txring[i].length & 0x7ff; + + sc->lmc_device->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->extra_stats.tx_tbusy0++; + + +#ifdef DEBUG + sc->extra_stats.dirtyTx = badtx; + sc->extra_stats.lmc_next_tx = sc->lmc_next_tx; + sc->extra_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 0x002: + 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 netdev_tx_t lmc_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + lmc_softc_t *sc = dev_to_sc(dev); + u32 flag; + int entry; + unsigned long flags; + + lmc_trace(dev, "lmc_start_xmit in"); + + 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->extra_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->extra_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); + + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + lmc_trace(dev, "lmc_start_xmit_out"); + return NETDEV_TX_OK; +} + + +static int lmc_rx(struct net_device *dev) +{ + lmc_softc_t *sc = dev_to_sc(dev); + int i; + int rx_work_limit = LMC_RXDESCS; + int rxIntLoopCnt; /* debug -baz */ + int localLengthErrCnt = 0; + long stat; + struct sk_buff *skb, *nsb; + u16 len; + + lmc_trace(dev, "lmc_rx in"); + + lmc_led_on(sc, LMC_DS3_LED3); + + rxIntLoopCnt = 0; /* debug -baz */ + + i = sc->lmc_next_rx % LMC_RXDESCS; + + 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->lmc_device->stats.rx_length_errors++; + goto skip_packet; + } + } + + if (stat & 0x00000008) { /* Catch a dribbling bit error */ + sc->lmc_device->stats.rx_errors++; + sc->lmc_device->stats.rx_frame_errors++; + goto skip_packet; + } + + + if (stat & 0x00000004) { /* Catch a CRC error by the Xilinx */ + sc->lmc_device->stats.rx_errors++; + sc->lmc_device->stats.rx_crc_errors++; + goto skip_packet; + } + + if (len > LMC_PKT_BUF_SZ) { + sc->lmc_device->stats.rx_length_errors++; + localLengthErrCnt++; + goto skip_packet; + } + + if (len < sc->lmc_crcSize + 2) { + sc->lmc_device->stats.rx_length_errors++; + sc->extra_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) { + 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(skb_tail_pointer(nsb)); + } + sc->failed_recv_alloc = 1; + goto skip_packet; + } + + sc->lmc_device->stats.rx_packets++; + sc->lmc_device->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_reset_mac_header(skb); + /* skb_reset_network_header(skb); */ + 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(skb_tail_pointer(nsb)); + /* 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->extra_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; + } + skb_copy_from_linear_data(skb, skb_put(nsb, len), len); + + nsb->protocol = lmc_proto_type(sc, nsb); + skb_reset_mac_header(nsb); + /* skb_reset_network_header(nsb); */ + 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->extra_stats.rx_BadPktSurgeCnt++; + LMC_EVENT_LOG(LMC_EVENT_BADPKTSURGE, localLengthErrCnt, + sc->extra_stats.rx_BadPktSurgeCnt); + } */ + + /* save max count of receive descriptors serviced */ + if (rxIntLoopCnt > sc->extra_stats.rxIntLoopCnt) + sc->extra_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) +{ + lmc_softc_t *sc = dev_to_sc(dev); + unsigned long flags; + + lmc_trace(dev, "lmc_get_stats in"); + + spin_lock_irqsave(&sc->lmc_lock, flags); + + sc->lmc_device->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 &sc->lmc_device->stats; +} + +static struct pci_driver lmc_driver = { + .name = "lmc", + .id_table = lmc_pci_tbl, + .probe = lmc_init_one, + .remove = lmc_remove_one, +}; + +module_pci_driver(lmc_driver); + +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_tailroom(skb); + + /* 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 + */ + if (i != 0) { + 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->lmc_device->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, u32 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, u32 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, u32 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, u32 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->extra_stats.resetCount++; + lmc_trace(sc->lmc_device, "lmc_reset out"); +} + +static void lmc_dec_reset(lmc_softc_t * const sc) /*fold00*/ +{ + u32 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) +{ + lmc_softc_t *sc = dev_to_sc(dev); + u32 csr6; + unsigned long flags; + + lmc_trace(dev, "lmc_driver_timeout in"); + + spin_lock_irqsave(&sc->lmc_lock, flags); + + printk("%s: Xmitter busy|\n", dev->name); + + sc->extra_stats.tx_tbusy_calls++; + if (jiffies - dev_trans_start(dev) < 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->extra_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->lmc_device->stats.tx_errors++; + sc->extra_stats.tx_ProcTimeout++; /* -baz */ + + netif_trans_update(dev); /* prevent tx timeout */ + +bug_out: + + spin_unlock_irqrestore(&sc->lmc_lock, flags); + + lmc_trace(dev, "lmc_driver_timeout out"); + + +} diff --git a/drivers/net/wan/lmc/lmc_media.c b/drivers/net/wan/lmc/lmc_media.c new file mode 100644 index 000000000..cffe23bd1 --- /dev/null +++ b/drivers/net/wan/lmc/lmc_media.c @@ -0,0 +1,1212 @@ +/* $Id: lmc_media.c,v 1.13 2000/04/11 05:25:26 asj Exp $ */ + +#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/interrupt.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 <asm/processor.h> /* Processor type for cache alignment. */ +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/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. + */ + +/* + * 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 *, u32, u32, u32, u32, u32); + +lmc_media_t lmc_ds3_media = { + .init = lmc_ds3_init, /* special media init stuff */ + .defaults = lmc_ds3_default, /* reset to default state */ + .set_status = lmc_ds3_set_status, /* reset status to state provided */ + .set_clock_source = lmc_dummy_set_1, /* set clock source */ + .set_speed = lmc_dummy_set2_1, /* set line speed */ + .set_cable_length = lmc_ds3_set_100ft, /* set cable length */ + .set_scrambler = lmc_ds3_set_scram, /* set scrambler */ + .get_link_status = lmc_ds3_get_link_status, /* get link status */ + .set_link_status = lmc_dummy_set_1, /* set link status */ + .set_crc_length = lmc_ds3_set_crc_length, /* set CRC length */ + .set_circuit_type = lmc_dummy_set_1, /* set T1 or E1 circuit type */ + .watchdog = lmc_ds3_watchdog +}; + +lmc_media_t lmc_hssi_media = { + .init = lmc_hssi_init, /* special media init stuff */ + .defaults = lmc_hssi_default, /* reset to default state */ + .set_status = lmc_hssi_set_status, /* reset status to state provided */ + .set_clock_source = lmc_hssi_set_clock, /* set clock source */ + .set_speed = lmc_dummy_set2_1, /* set line speed */ + .set_cable_length = lmc_dummy_set_1, /* set cable length */ + .set_scrambler = lmc_dummy_set_1, /* set scrambler */ + .get_link_status = lmc_hssi_get_link_status, /* get link status */ + .set_link_status = lmc_hssi_set_link_status, /* set link status */ + .set_crc_length = lmc_hssi_set_crc_length, /* set CRC length */ + .set_circuit_type = lmc_dummy_set_1, /* set T1 or E1 circuit type */ + .watchdog = lmc_hssi_watchdog +}; + +lmc_media_t lmc_ssi_media = { + .init = lmc_ssi_init, /* special media init stuff */ + .defaults = lmc_ssi_default, /* reset to default state */ + .set_status = lmc_ssi_set_status, /* reset status to state provided */ + .set_clock_source = lmc_ssi_set_clock, /* set clock source */ + .set_speed = lmc_ssi_set_speed, /* set line speed */ + .set_cable_length = lmc_dummy_set_1, /* set cable length */ + .set_scrambler = lmc_dummy_set_1, /* set scrambler */ + .get_link_status = lmc_ssi_get_link_status, /* get link status */ + .set_link_status = lmc_ssi_set_link_status, /* set link status */ + .set_crc_length = lmc_ssi_set_crc_length, /* set CRC length */ + .set_circuit_type = lmc_dummy_set_1, /* set T1 or E1 circuit type */ + .watchdog = lmc_ssi_watchdog +}; + +lmc_media_t lmc_t1_media = { + .init = lmc_t1_init, /* special media init stuff */ + .defaults = lmc_t1_default, /* reset to default state */ + .set_status = lmc_t1_set_status, /* reset status to state provided */ + .set_clock_source = lmc_t1_set_clock, /* set clock source */ + .set_speed = lmc_dummy_set2_1, /* set line speed */ + .set_cable_length = lmc_dummy_set_1, /* set cable length */ + .set_scrambler = lmc_dummy_set_1, /* set scrambler */ + .get_link_status = lmc_t1_get_link_status, /* get link status */ + .set_link_status = lmc_dummy_set_1, /* set link status */ + .set_crc_length = lmc_t1_set_crc_length, /* set CRC length */ + .set_circuit_type = lmc_t1_set_circuit_type, /* set T1 or E1 circuit type */ + .watchdog = 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) +{ + u16 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) +{ + u16 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) +{ + u16 link_status; + u32 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->extra_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, u32 n, u32 m, u32 v, u32 x, u32 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) +{ + u16 mii17 = lmc_mii_readreg(sc, 0, 17); + if (((mii17 >> 3) & 7) == 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) +{ + u16 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) +{ + u16 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) + sc->ictl.keepalive_onoff = LMC_CTL_ON; +} diff --git a/drivers/net/wan/lmc/lmc_proto.c b/drivers/net/wan/lmc/lmc_proto.c new file mode 100644 index 000000000..f600075e8 --- /dev/null +++ b/drivers/net/wan/lmc/lmc_proto.c @@ -0,0 +1,135 @@ + /* + * 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/interrupt.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 <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" + +// attach +void lmc_proto_attach(lmc_softc_t *sc) /*FOLD00*/ +{ + lmc_trace(sc->lmc_device, "lmc_proto_attach in"); + if (sc->if_type == LMC_NET) { + struct net_device *dev = sc->lmc_device; + /* + * They set a few basics because they don't use HDLC + */ + dev->flags |= IFF_POINTOPOINT; + dev->hard_header_len = 0; + dev->addr_len = 0; + } + lmc_trace(sc->lmc_device, "lmc_proto_attach out"); +} + +int lmc_proto_ioctl(lmc_softc_t *sc, struct ifreq *ifr, int cmd) +{ + lmc_trace(sc->lmc_device, "lmc_proto_ioctl"); + if (sc->if_type == LMC_PPP) + return hdlc_ioctl(sc->lmc_device, ifr, cmd); + return -EOPNOTSUPP; +} + +int lmc_proto_open(lmc_softc_t *sc) +{ + int ret = 0; + + lmc_trace(sc->lmc_device, "lmc_proto_open in"); + + if (sc->if_type == LMC_PPP) { + ret = hdlc_open(sc->lmc_device); + if (ret < 0) + printk(KERN_WARNING "%s: HDLC open failed: %d\n", + sc->name, ret); + } + + lmc_trace(sc->lmc_device, "lmc_proto_open out"); + return ret; +} + +void lmc_proto_close(lmc_softc_t *sc) +{ + lmc_trace(sc->lmc_device, "lmc_proto_close in"); + + if (sc->if_type == LMC_PPP) + hdlc_close(sc->lmc_device); + + lmc_trace(sc->lmc_device, "lmc_proto_close out"); +} + +__be16 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 hdlc_type_trans(skb, sc->lmc_device); + 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: + 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 000000000..bb098e443 --- /dev/null +++ b/drivers/net/wan/lmc/lmc_proto.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LMC_PROTO_H_ +#define _LMC_PROTO_H_ + +#include <linux/hdlc.h> + +void lmc_proto_attach(lmc_softc_t *sc); +int lmc_proto_ioctl(lmc_softc_t *sc, struct ifreq *ifr, int cmd); +int lmc_proto_open(lmc_softc_t *sc); +void lmc_proto_close(lmc_softc_t *sc); +__be16 lmc_proto_type(lmc_softc_t *sc, struct sk_buff *skb); +void lmc_proto_netif(lmc_softc_t *sc, struct sk_buff *skb); + +static inline lmc_softc_t* dev_to_sc(struct net_device *dev) +{ + return (lmc_softc_t *)dev_to_hdlc(dev)->priv; +} + +#endif diff --git a/drivers/net/wan/lmc/lmc_var.h b/drivers/net/wan/lmc/lmc_var.h new file mode 100644 index 000000000..a1d202d8a --- /dev/null +++ b/drivers/net/wan/lmc/lmc_var.h @@ -0,0 +1,470 @@ +#ifndef _LMC_VAR_H_ +#define _LMC_VAR_H_ + + /* + * 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> + +/* + * 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 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 ((u32)(0x000007FF)) +#define LMC_TDES_SECOND_BUFFER_SIZE ((u32)(0x003FF800)) +#define LMC_TDES_HASH_FILTERING ((u32)(0x00400000)) +#define LMC_TDES_DISABLE_PADDING ((u32)(0x00800000)) +#define LMC_TDES_SECOND_ADDR_CHAINED ((u32)(0x01000000)) +#define LMC_TDES_END_OF_RING ((u32)(0x02000000)) +#define LMC_TDES_ADD_CRC_DISABLE ((u32)(0x04000000)) +#define LMC_TDES_SETUP_PACKET ((u32)(0x08000000)) +#define LMC_TDES_INVERSE_FILTERING ((u32)(0x10000000)) +#define LMC_TDES_FIRST_SEGMENT ((u32)(0x20000000)) +#define LMC_TDES_LAST_SEGMENT ((u32)(0x40000000)) +#define LMC_TDES_INTERRUPT_ON_COMPLETION ((u32)(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 ((u32)(0x00000001)) +#define LMC_RDES_CRC_ERROR ((u32)(0x00000002)) +#define LMC_RDES_DRIBBLING_BIT ((u32)(0x00000004)) +#define LMC_RDES_REPORT_ON_MII_ERR ((u32)(0x00000008)) +#define LMC_RDES_RCV_WATCHDOG_TIMEOUT ((u32)(0x00000010)) +#define LMC_RDES_FRAME_TYPE ((u32)(0x00000020)) +#define LMC_RDES_COLLISION_SEEN ((u32)(0x00000040)) +#define LMC_RDES_FRAME_TOO_LONG ((u32)(0x00000080)) +#define LMC_RDES_LAST_DESCRIPTOR ((u32)(0x00000100)) +#define LMC_RDES_FIRST_DESCRIPTOR ((u32)(0x00000200)) +#define LMC_RDES_MULTICAST_FRAME ((u32)(0x00000400)) +#define LMC_RDES_RUNT_FRAME ((u32)(0x00000800)) +#define LMC_RDES_DATA_TYPE ((u32)(0x00003000)) +#define LMC_RDES_LENGTH_ERROR ((u32)(0x00004000)) +#define LMC_RDES_ERROR_SUMMARY ((u32)(0x00008000)) +#define LMC_RDES_FRAME_LENGTH ((u32)(0x3FFF0000)) +#define LMC_RDES_OWN_BIT ((u32)(0x80000000)) + +#define RDES_FRAME_LENGTH_BIT_NUMBER 16 + +#define LMC_RDES_ERROR_MASK ( (u32)( \ + LMC_RDES_OVERFLOW \ + | LMC_RDES_DRIBBLING_BIT \ + | LMC_RDES_REPORT_ON_MII_ERR \ + | LMC_RDES_COLLISION_SEEN ) ) + + +/* + * Ioctl info + */ + +typedef struct { + u32 n; + u32 m; + u32 v; + u32 x; + u32 r; + u32 f; + u32 exact; +} lmc_av9110_t; + +/* + * Common structure passed to the ioctl code. + */ +struct lmc___ctl { + u32 cardtype; + u32 clock_source; /* HSSI, T1 */ + u32 clock_rate; /* T1 */ + u32 crc_length; + u32 cable_length; /* DS3 */ + u32 scrambler_onoff; /* DS3 */ + u32 cable_type; /* T1 */ + u32 keepalive_onoff; /* protocol */ + u32 ticks; /* ticks/sec */ + union { + lmc_av9110_t ssi; + } cardspec; + u32 circuit_type; /* T1 or E1 */ +}; + + +/* + * Careful, 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 + +struct lmc_extra_statistics +{ + u32 version_size; + u32 lmc_cardtype; + + u32 tx_ProcTimeout; + u32 tx_IntTimeout; + u32 tx_NoCompleteCnt; + u32 tx_MaxXmtsB4Int; + u32 tx_TimeoutCnt; + u32 tx_OutOfSyncPtr; + u32 tx_tbusy0; + u32 tx_tbusy1; + u32 tx_tbusy_calls; + u32 resetCount; + u32 lmc_txfull; + u32 tbusy; + u32 dirtyTx; + u32 lmc_next_tx; + u32 otherTypeCnt; + u32 lastType; + u32 lastTypeOK; + u32 txLoopCnt; + u32 usedXmtDescripCnt; + u32 txIndexCnt; + u32 rxIntLoopCnt; + + u32 rx_SmallPktCnt; + u32 rx_BadPktSurgeCnt; + u32 rx_BuffAllocErr; + u32 tx_lossOfClockCnt; + + /* T1 error counters */ + u32 framingBitErrorCount; + u32 lineCodeViolationCount; + + u32 lossOfFrameCount; + u32 changeOfFrameAlignmentCount; + u32 severelyErroredFrameCount; + + u32 check; +}; + +typedef struct lmc_xinfo { + u32 Magic0; /* BEEFCAFE */ + + u32 PciCardType; + u32 PciSlotNumber; /* PCI slot number */ + + u16 DriverMajorVersion; + u16 DriverMinorVersion; + u16 DriverSubVersion; + + u16 XilinxRevisionNumber; + u16 MaxFrameSize; + + u16 t1_alarm1_status; + u16 t1_alarm2_status; + + int link_status; + u32 mii_reg16; + + u32 Magic1; /* DEADBEEF */ +} LMC_XINFO; + + +/* + * forward decl + */ +struct lmc___softc { + char *name; + u8 board_idx; + struct lmc_extra_statistics extra_stats; + struct net_device *lmc_device; + + int hang, rxdesc, bad_packet, some_counter; + u32 txgo; + struct lmc_regfile_t lmc_csrs; + volatile u32 lmc_txtick; + volatile u32 lmc_rxtick; + u32 lmc_flags; + u32 lmc_intrmask; /* our copy of csr_intr */ + u32 lmc_cmdmode; /* our copy of csr_cmdmode */ + u32 lmc_busmode; /* our copy of csr_busmode */ + u32 lmc_gpio_io; /* state of in/out settings */ + u32 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; + u16 lmc_miireg16; + int lmc_ok; + int last_link_status; + int lmc_cardtype; + u32 last_frameerr; + lmc_media_t *lmc_media; + struct timer_list timer; + lmc_ctl_t ictl; + u32 TxDescriptControlInit; + + int tx_TimeoutInd; /* additional driver state */ + int tx_TimeoutDisplay; + unsigned int lastlmc_taint_tx; + int lasttx_packets; + u32 tx_clockState; + u32 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; + u16 if_type; /* HDLC/PPP or NET */ + + /* 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 RECEIVE_ALL 0x40000000 /* Receive 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 Forward (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 ((u32)(0x00000000)) +#define DESC_OWNED_BY_DC21X4 ((u32)(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 LMC_MTU 1500 + +#define LMC_CRC_LEN_16 2 /* 16-bit CRC */ +#define LMC_CRC_LEN_32 4 + +#endif /* _LMC_VAR_H_ */ diff --git a/drivers/net/wan/n2.c b/drivers/net/wan/n2.c new file mode 100644 index 000000000..c8f4517db --- /dev/null +++ b/drivers/net/wan/n2.c @@ -0,0 +1,565 @@ +/* + * 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://www.kernel.org/pub/linux/utils/net/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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/capability.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; /* 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 "hd64570.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 const struct net_device_ops n2_ops = { + .ndo_open = n2_open, + .ndo_stop = n2_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = n2_ioctl, +}; + +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) { + pr_err("invalid I/O port value\n"); + return -ENODEV; + } + + if (irq < 3 || irq > 15 || irq == 6) /* FIXME */ { + pr_err("invalid IRQ value\n"); + return -ENODEV; + } + + if (winbase < 0xA0000 || winbase > 0xFFFFF || (winbase & 0xFFF) != 0) { + pr_err("invalid RAM value\n"); + return -ENODEV; + } + + card = kzalloc(sizeof(card_t), GFP_KERNEL); + if (card == NULL) + return -ENOBUFS; + + 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) { + pr_err("unable to allocate memory\n"); + n2_destroy_card(card); + return -ENOMEM; + } + + if (!request_region(io, N2_IOPORTS, devname)) { + pr_err("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)) { + pr_err("could not allocate IRQ\n"); + n2_destroy_card(card); + return -EBUSY; + } + card->irq = irq; + + if (!request_mem_region(winbase, USE_WINDOWSIZE, devname)) { + pr_err("could not request RAM window\n"); + n2_destroy_card(card); + return -EBUSY; + } + card->phy_winbase = winbase; + card->winbase = ioremap(winbase, USE_WINDOWSIZE); + if (!card->winbase) { + pr_err("ioremap() failed\n"); + n2_destroy_card(card); + return -EFAULT; + } + + 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: + pr_err("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); + + pr_info("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) { + pr_err("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); + dev->irq = irq; + dev->mem_start = winbase; + dev->mem_end = winbase + USE_WINDOWSIZE - 1; + dev->tx_queue_len = 50; + dev->netdev_ops = &n2_ops; + hdlc->attach = sca_attach; + hdlc->xmit = sca_xmit; + port->settings.clock_type = CLOCK_EXT; + port->card = card; + + if (register_hdlc_device(dev)) { + pr_warn("unable to register hdlc device\n"); + port->card = NULL; + n2_destroy_card(card); + return -ENOBUFS; + } + sca_init_port(port); /* Set up SCA memory */ + + netdev_info(dev, "RISCom/N2 node %d\n", port->phy_node); + } + + *new_card = card; + new_card = &card->next_card; + + return 0; +} + + + +static int __init n2_init(void) +{ + if (hw==NULL) { +#ifdef MODULE + pr_info("no card initialized\n"); +#endif + return -EINVAL; /* no parameters specified, abort */ + } + + pr_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 : -EINVAL; + }while(*hw++ == ':'); + + pr_err("invalid hardware parameters\n"); + return first_card ? 0 : -EINVAL; +} + + +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); +MODULE_PARM_DESC(hw, "io,irq,ram,ports:io,irq,..."); diff --git a/drivers/net/wan/pc300too.c b/drivers/net/wan/pc300too.c new file mode 100644 index 000000000..b9b934b77 --- /dev/null +++ b/drivers/net/wan/pc300too.c @@ -0,0 +1,534 @@ +/* + * Cyclades PC300 synchronous serial card driver for Linux + * + * Copyright (C) 2000-2008 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://www.kernel.org/pub/linux/utils/net/hdlc/>. + * + * Sources of information: + * Hitachi HD64572 SCA-II User's Manual + * Original Cyclades PC300 Linux driver + * + * This driver currently supports only PC300/RSV (V.24/V.35) and + * PC300/X21 cards. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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 <linux/delay.h> +#include <asm/io.h> + +#include "hd64572.h" + +#undef DEBUG_PKT +#define DEBUG_RINGS + +#define PC300_PLX_SIZE 0x80 /* PLX control window size (128 B) */ +#define PC300_SCA_SIZE 0x400 /* SCA window size (1 KB) */ +#define MAX_TX_BUFFERS 10 + +static int pci_clock_freq = 33000000; +static int use_crystal_clock = 0; +static unsigned int CLOCK_BASE; + +/* Masks to access the init_ctrl PLX register */ +#define PC300_CLKSEL_MASK (0x00000004UL) +#define PC300_CHMEDIA_MASK(port) (0x00000020UL << ((port) * 3)) +#define PC300_CTYPE_MASK (0x00000800UL) + + +enum { PC300_RSV = 1, PC300_X21, PC300_TE }; /* card types */ + +/* + * PLX PCI9050-1 local configuration and shared runtime registers. + * This structure can be used to access 9050 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 */ +}plx9050; + + + +typedef struct port_s { + struct napi_struct napi; + struct net_device *netdev; + 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; + unsigned int iface; + 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 chan; /* physical port # - 0 or 1 */ +}port_t; + + + +typedef struct card_s { + int type; /* RSV, X21, etc. */ + int n_ports; /* 1 or 2 ports */ + u8 __iomem *rambase; /* buffer memory base (virtual) */ + u8 __iomem *scabase; /* SCA memory base (virtual) */ + plx9050 __iomem *plxbase; /* PLX registers memory base (virtual) */ + u32 init_ctrl_value; /* Saved value - 9050 bug workaround */ + 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 get_port(card, port) ((port) < (card)->n_ports ? \ + (&(card)->ports[port]) : (NULL)) + +#include "hd64572.c" + + +static void pc300_set_iface(port_t *port) +{ + card_t *card = port->card; + u32 __iomem * init_ctrl = &card->plxbase->init_ctrl; + u16 msci = get_msci(port); + u8 rxs = port->rxs & CLK_BRG_MASK; + u8 txs = port->txs & CLK_BRG_MASK; + + sca_out(EXS_TES1, (port->chan ? MSCI1_OFFSET : MSCI0_OFFSET) + EXS, + port->card); + 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); + + if (port->card->type == PC300_RSV) { + if (port->iface == IF_IFACE_V35) + writel(card->init_ctrl_value | + PC300_CHMEDIA_MASK(port->chan), init_ctrl); + else + writel(card->init_ctrl_value & + ~PC300_CHMEDIA_MASK(port->chan), init_ctrl); + } +} + + + +static int pc300_open(struct net_device *dev) +{ + port_t *port = dev_to_port(dev); + + int result = hdlc_open(dev); + if (result) + return result; + + sca_open(dev); + pc300_set_iface(port); + return 0; +} + + + +static int pc300_close(struct net_device *dev) +{ + sca_close(dev); + hdlc_close(dev); + return 0; +} + + + +static int pc300_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; + int new_type; + 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); + + if (ifr->ifr_settings.type == IF_GET_IFACE) { + ifr->ifr_settings.type = port->iface; + 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; + + } + + if (port->card->type == PC300_X21 && + (ifr->ifr_settings.type == IF_IFACE_SYNC_SERIAL || + ifr->ifr_settings.type == IF_IFACE_X21)) + new_type = IF_IFACE_X21; + + else if (port->card->type == PC300_RSV && + (ifr->ifr_settings.type == IF_IFACE_SYNC_SERIAL || + ifr->ifr_settings.type == IF_IFACE_V35)) + new_type = IF_IFACE_V35; + + else if (port->card->type == PC300_RSV && + ifr->ifr_settings.type == IF_IFACE_V24) + new_type = IF_IFACE_V24; + + else + return hdlc_ioctl(dev, ifr, cmd); + + 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 */ + port->iface = new_type; + pc300_set_iface(port); + return 0; +} + + + +static void pc300_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) + unregister_hdlc_device(card->ports[i].netdev); + + 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); + if (card->ports[0].netdev) + free_netdev(card->ports[0].netdev); + if (card->ports[1].netdev) + free_netdev(card->ports[1].netdev); + kfree(card); +} + +static const struct net_device_ops pc300_ops = { + .ndo_open = pc300_open, + .ndo_stop = pc300_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = pc300_ioctl, +}; + +static int pc300_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + card_t *card; + u32 __iomem *p; + int i; + u32 ramsize; + u32 ramphys; /* buffer memory base */ + u32 scaphys; /* SCA memory base */ + u32 plxphys; /* PLX registers memory base */ + + i = pci_enable_device(pdev); + if (i) + return i; + + i = pci_request_regions(pdev, "PC300"); + if (i) { + pci_disable_device(pdev); + return i; + } + + card = kzalloc(sizeof(card_t), GFP_KERNEL); + if (card == NULL) { + pci_release_regions(pdev); + pci_disable_device(pdev); + return -ENOBUFS; + } + pci_set_drvdata(pdev, card); + + if (pci_resource_len(pdev, 0) != PC300_PLX_SIZE || + pci_resource_len(pdev, 2) != PC300_SCA_SIZE || + pci_resource_len(pdev, 3) < 16384) { + pr_err("invalid card EEPROM parameters\n"); + pc300_pci_remove_one(pdev); + return -EFAULT; + } + + plxphys = pci_resource_start(pdev, 0) & PCI_BASE_ADDRESS_MEM_MASK; + card->plxbase = ioremap(plxphys, PC300_PLX_SIZE); + + scaphys = pci_resource_start(pdev, 2) & PCI_BASE_ADDRESS_MEM_MASK; + card->scabase = ioremap(scaphys, PC300_SCA_SIZE); + + ramphys = pci_resource_start(pdev, 3) & PCI_BASE_ADDRESS_MEM_MASK; + card->rambase = pci_ioremap_bar(pdev, 3); + + if (card->plxbase == NULL || + card->scabase == NULL || + card->rambase == NULL) { + pr_err("ioremap() failed\n"); + pc300_pci_remove_one(pdev); + return -ENOMEM; + } + + /* PLX PCI 9050 workaround for local configuration register read bug */ + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, scaphys); + card->init_ctrl_value = readl(&((plx9050 __iomem *)card->scabase)->init_ctrl); + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, plxphys); + + if (pdev->device == PCI_DEVICE_ID_PC300_TE_1 || + pdev->device == PCI_DEVICE_ID_PC300_TE_2) + card->type = PC300_TE; /* not fully supported */ + else if (card->init_ctrl_value & PC300_CTYPE_MASK) + card->type = PC300_X21; + else + card->type = PC300_RSV; + + if (pdev->device == PCI_DEVICE_ID_PC300_RX_1 || + pdev->device == PCI_DEVICE_ID_PC300_TE_1) + card->n_ports = 1; + else + card->n_ports = 2; + + for (i = 0; i < card->n_ports; i++) + if (!(card->ports[i].netdev = alloc_hdlcdev(&card->ports[i]))) { + pr_err("unable to allocate memory\n"); + pc300_pci_remove_one(pdev); + return -ENOMEM; + } + + /* Reset PLX */ + p = &card->plxbase->init_ctrl; + writel(card->init_ctrl_value | 0x40000000, p); + readl(p); /* Flush the write - do not use sca_flush */ + udelay(1); + + writel(card->init_ctrl_value, p); + readl(p); /* Flush the write - do not use sca_flush */ + udelay(1); + + /* Reload Config. Registers from EEPROM */ + writel(card->init_ctrl_value | 0x20000000, p); + readl(p); /* Flush the write - do not use sca_flush */ + udelay(1); + + writel(card->init_ctrl_value, 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)); + + if (use_crystal_clock) + card->init_ctrl_value &= ~PC300_CLKSEL_MASK; + else + card->init_ctrl_value |= PC300_CLKSEL_MASK; + + writel(card->init_ctrl_value, &card->plxbase->init_ctrl); + /* number of TX + RX buffers for one port */ + i = ramsize / (card->n_ports * (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 = card->n_ports * sizeof(pkt_desc) * + (card->tx_ring_buffers + card->rx_ring_buffers); + + pr_info("PC300/%s, %u KB RAM at 0x%x, IRQ%u, using %u TX + %u RX packets rings\n", + card->type == PC300_X21 ? "X21" : + card->type == PC300_TE ? "TE" : "RSV", + ramsize / 1024, ramphys, pdev->irq, + card->tx_ring_buffers, card->rx_ring_buffers); + + if (card->tx_ring_buffers < 1) { + pr_err("RAM test failed\n"); + pc300_pci_remove_one(pdev); + return -EFAULT; + } + + /* Enable interrupts on the PCI bridge, LINTi1 active low */ + writew(0x0041, &card->plxbase->intr_ctrl_stat); + + /* Allocate IRQ */ + if (request_irq(pdev->irq, sca_intr, IRQF_SHARED, "pc300", card)) { + pr_warn("could not allocate IRQ%d\n", pdev->irq); + pc300_pci_remove_one(pdev); + return -EBUSY; + } + card->irq = pdev->irq; + + sca_init(card, 0); + + // COTE not set - allows better TX DMA settings + // sca_out(sca_in(PCR, card) | PCR_COTE, PCR, card); + + sca_out(0x10, BTCR, card); + + for (i = 0; i < card->n_ports; i++) { + port_t *port = &card->ports[i]; + struct net_device *dev = port->netdev; + hdlc_device *hdlc = dev_to_hdlc(dev); + port->chan = i; + + spin_lock_init(&port->lock); + dev->irq = card->irq; + dev->mem_start = ramphys; + dev->mem_end = ramphys + ramsize - 1; + dev->tx_queue_len = 50; + dev->netdev_ops = &pc300_ops; + hdlc->attach = sca_attach; + hdlc->xmit = sca_xmit; + port->settings.clock_type = CLOCK_EXT; + port->card = card; + if (card->type == PC300_X21) + port->iface = IF_IFACE_X21; + else + port->iface = IF_IFACE_V35; + + sca_init_port(port); + if (register_hdlc_device(dev)) { + pr_err("unable to register hdlc device\n"); + port->card = NULL; + pc300_pci_remove_one(pdev); + return -ENOBUFS; + } + + netdev_info(dev, "PC300 channel %d\n", port->chan); + } + return 0; +} + + + +static const struct pci_device_id pc300_pci_tbl[] = { + { PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_PC300_RX_1, PCI_ANY_ID, + PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_PC300_RX_2, PCI_ANY_ID, + PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_PC300_TE_1, PCI_ANY_ID, + PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_CYCLADES, PCI_DEVICE_ID_PC300_TE_2, PCI_ANY_ID, + PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + + +static struct pci_driver pc300_pci_driver = { + .name = "PC300", + .id_table = pc300_pci_tbl, + .probe = pc300_pci_init_one, + .remove = pc300_pci_remove_one, +}; + + +static int __init pc300_init_module(void) +{ + if (pci_clock_freq < 1000000 || pci_clock_freq > 80000000) { + pr_err("Invalid PCI clock frequency\n"); + return -EINVAL; + } + if (use_crystal_clock != 0 && use_crystal_clock != 1) { + pr_err("Invalid 'use_crystal_clock' value\n"); + return -EINVAL; + } + + CLOCK_BASE = use_crystal_clock ? 24576000 : pci_clock_freq; + + return pci_register_driver(&pc300_pci_driver); +} + + + +static void __exit pc300_cleanup_module(void) +{ + pci_unregister_driver(&pc300_pci_driver); +} + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("Cyclades PC300 serial port driver"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(pci, pc300_pci_tbl); +module_param(pci_clock_freq, int, 0444); +MODULE_PARM_DESC(pci_clock_freq, "System PCI clock frequency in Hz"); +module_param(use_crystal_clock, int, 0444); +MODULE_PARM_DESC(use_crystal_clock, + "Use 24.576 MHz clock instead of PCI clock"); +module_init(pc300_init_module); +module_exit(pc300_cleanup_module); diff --git a/drivers/net/wan/pci200syn.c b/drivers/net/wan/pci200syn.c new file mode 100644 index 000000000..4e437c599 --- /dev/null +++ b/drivers/net/wan/pci200syn.c @@ -0,0 +1,454 @@ +/* + * Goramo PCI200SYN synchronous serial card driver for Linux + * + * Copyright (C) 2002-2008 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://www.kernel.org/pub/linux/utils/net/hdlc/> + * + * Sources of information: + * Hitachi HD64572 SCA-II User's Manual + * PLX Technology Inc. PCI9052 Data Book + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/capability.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 <linux/pci.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include "hd64572.h" + +#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 MAX_TX_BUFFERS 10 + +static int pci_clock_freq = 33000000; +#define CLOCK_BASE pci_clock_freq + +/* + * 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 napi_struct napi; + struct net_device *netdev; + 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 chan; /* 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 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 "hd64572.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, (port->chan ? MSCI1_OFFSET : MSCI0_OFFSET) + EXS, + port->card); + 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->card); + return 0; +} + + + +static int pci200_close(struct net_device *dev) +{ + sca_close(dev); + sca_flush(dev_to_port(dev)->card); + 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->card); + 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) + unregister_hdlc_device(card->ports[i].netdev); + + 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); + if (card->ports[0].netdev) + free_netdev(card->ports[0].netdev); + if (card->ports[1].netdev) + free_netdev(card->ports[1].netdev); + kfree(card); +} + +static const struct net_device_ops pci200_ops = { + .ndo_open = pci200_open, + .ndo_stop = pci200_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = pci200_ioctl, +}; + +static int pci200_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + card_t *card; + u32 __iomem *p; + int i; + u32 ramsize; + u32 ramphys; /* buffer memory base */ + u32 scaphys; /* SCA memory base */ + u32 plxphys; /* PLX registers memory base */ + + i = pci_enable_device(pdev); + if (i) + return i; + + i = pci_request_regions(pdev, "PCI200SYN"); + if (i) { + pci_disable_device(pdev); + return i; + } + + card = kzalloc(sizeof(card_t), GFP_KERNEL); + if (card == NULL) { + pci_release_regions(pdev); + pci_disable_device(pdev); + return -ENOBUFS; + } + pci_set_drvdata(pdev, card); + card->ports[0].netdev = alloc_hdlcdev(&card->ports[0]); + card->ports[1].netdev = alloc_hdlcdev(&card->ports[1]); + if (!card->ports[0].netdev || !card->ports[1].netdev) { + pr_err("unable to allocate memory\n"); + pci200_pci_remove_one(pdev); + return -ENOMEM; + } + + if (pci_resource_len(pdev, 0) != PCI200SYN_PLX_SIZE || + pci_resource_len(pdev, 2) != PCI200SYN_SCA_SIZE || + pci_resource_len(pdev, 3) < 16384) { + pr_err("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 = pci_ioremap_bar(pdev, 3); + + if (card->plxbase == NULL || + card->scabase == NULL || + card->rambase == NULL) { + pr_err("ioremap() failed\n"); + pci200_pci_remove_one(pdev); + return -EFAULT; + } + + /* 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); + + pr_info("%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) { + pr_err("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, IRQF_SHARED, "pci200syn", card)) { + pr_warn("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->netdev; + hdlc_device *hdlc = dev_to_hdlc(dev); + port->chan = i; + + spin_lock_init(&port->lock); + dev->irq = card->irq; + dev->mem_start = ramphys; + dev->mem_end = ramphys + ramsize - 1; + dev->tx_queue_len = 50; + dev->netdev_ops = &pci200_ops; + hdlc->attach = sca_attach; + hdlc->xmit = sca_xmit; + port->settings.clock_type = CLOCK_EXT; + port->card = card; + sca_init_port(port); + if (register_hdlc_device(dev)) { + pr_err("unable to register hdlc device\n"); + port->card = NULL; + pci200_pci_remove_one(pdev); + return -ENOBUFS; + } + + netdev_info(dev, "PCI200SYN channel %d\n", port->chan); + } + + sca_flush(card); + return 0; +} + + + +static const struct pci_device_id pci200_pci_tbl[] = { + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_PLX, + PCI_DEVICE_ID_PLX_PCI200SYN, 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) +{ + if (pci_clock_freq < 1000000 || pci_clock_freq > 80000000) { + pr_err("Invalid PCI clock frequency\n"); + return -EINVAL; + } + return pci_register_driver(&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 000000000..8e8c4c0e1 --- /dev/null +++ b/drivers/net/wan/sbni.c @@ -0,0 +1,1623 @@ +/* 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/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/net_namespace.h> +#include <net/arp.h> +#include <net/Space.h> + +#include <asm/io.h> +#include <asm/types.h> +#include <asm/byteorder.h> +#include <asm/irq.h> +#include <linux/uaccess.h> + +#include "sbni.h" + +/* device private data */ + +struct net_local { + struct timer_list watchdog; + struct net_device *watchdog_dev; + + 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 netdev_tx_t sbni_start_xmit(struct sk_buff *, + struct net_device * ); +static int sbni_ioctl( struct net_device *, struct ifreq *, int ); +static void set_multicast_list( struct net_device * ); + +static irqreturn_t sbni_interrupt( int, void * ); +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(struct timer_list *); +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 + +static const char version[] = + "Granch SBNI12 driver ver 5.0.1 Jun 22 2001 Denis I.Timofeev.\n"; + +static bool skip_pci_probe __initdata = false; +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 *dest[5] __initdata = { &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 }; + +#define NET_LOCAL_LOCK(dev) (((struct net_local *)netdev_priv(dev))->lock) + +/* + * 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 { + pr_err("base address 0x%lx is busy, or adapter is malfunctional!\n", + dev->base_addr); + return -ENODEV; + } +} + +static const struct net_device_ops sbni_netdev_ops = { + .ndo_open = sbni_open, + .ndo_stop = sbni_close, + .ndo_start_xmit = sbni_start_xmit, + .ndo_set_rx_mode = set_multicast_list, + .ndo_do_ioctl = sbni_ioctl, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static void __init sbni_devsetup(struct net_device *dev) +{ + ether_setup( dev ); + dev->netdev_ops = &sbni_netdev_ops; +} + +int __init sbni_probe(int unit) +{ + struct net_device *dev; + int err; + + dev = alloc_netdev(sizeof(struct net_local), "sbni", + NET_NAME_UNKNOWN, sbni_devsetup); + if (!dev) + return -ENOMEM; + + dev->netdev_ops = &sbni_netdev_ops; + + 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; + } + pr_info_once("%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; +} + + +static 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; + + 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 ) ) { + if (pdev->subsystem_device != 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) + pr_warn( +"WARNING: The PCI BIOS assigned this PCI card to IRQ %d, which is unlikely to work!.\n" +"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 ) { + pr_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; + + /* Fill in sbni-specific dev fields. */ + nl = netdev_priv(dev); + if( !nl ) { + pr_err("%s: unable to get memory!\n", dev->name); + release_region( ioaddr, SBNI_IO_EXTENT ); + return NULL; + } + + memset( nl, 0, sizeof(struct net_local) ); + spin_lock_init( &nl->lock ); + + /* store MAC address (generate if that isn't known) */ + *(__be16 *)dev->dev_addr = htons( 0x00ff ); + *(__be32 *)(dev->dev_addr + 2) = htonl( 0x01000000 | + ((mac[num] ? + mac[num] : + (u32)((long)netdev_priv(dev))) & 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; + + pr_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]); + + pr_notice("%s: speed %d", + dev->name, + ((nl->state & FL_SLOW_MODE) ? 500000 : 2000000) + / (1 << nl->csr1.rate)); + + if( nl->delta_rxl == 0 ) + pr_cont(", receive level 0x%x (fixed)\n", nl->cur_rxl_index); + else + pr_cont(", receive level (auto)\n"); + +#ifdef CONFIG_SBNI_MULTILINE + nl->master = dev; + nl->link = NULL; +#endif + + sbni_cards[ num++ ] = dev; + return dev; +} + +/* -------------------------------------------------------------------------- */ + +#ifdef CONFIG_SBNI_MULTILINE + +static netdev_tx_t +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 = netdev_priv(p); + 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 NETDEV_TX_OK; + } + } + + return NETDEV_TX_BUSY; +} + +#else /* CONFIG_SBNI_MULTILINE */ + +static netdev_tx_t +sbni_start_xmit( struct sk_buff *skb, struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + + netif_stop_queue( dev ); + spin_lock( &nl->lock ); + + prepare_to_send( skb, dev ); + + spin_unlock( &nl->lock ); + return NETDEV_TX_OK; +} + +#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 net_device *dev = dev_id; + struct net_local *nl = netdev_priv(dev); + int repeat; + + spin_lock( &nl->lock ); + if( nl->second ) + spin_lock(&NET_LOCAL_LOCK(nl->second)); + + 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(&NET_LOCAL_LOCK(nl->second)); + spin_unlock( &nl->lock ); + return IRQ_HANDLED; +} + + +static void +handle_channel( struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + 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(&NET_LOCAL_LOCK(nl->master)); +#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) ) + netdev_err(dev, "internal error!\n"); + + /* 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(&NET_LOCAL_LOCK(nl->master)); +#endif +} + + +/* + * Routine returns 1 if it needs to acknowledge received frame. + * Empty frame received without errors won't be acknowledged. + */ + +static int +recv_frame( struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + unsigned long ioaddr = dev->base_addr; + + u32 crc = CRC32_INITIAL; + + unsigned framelen = 0, frameno, ack; + unsigned is_first, frame_ok = 0; + + 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 ); + } + + 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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + 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 = netdev_priv(dev); + + 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 + nl->master->stats.rx_errors++, + nl->master->stats.rx_missed_errors++; +#else + dev->stats.rx_errors++, + dev->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 + nl->master->stats.rx_errors++, + nl->master->stats.rx_crc_errors++; +#else + dev->stats.rx_errors++, + dev->stats.rx_crc_errors++; +#endif + + return frame_ok; +} + + +static inline void +send_complete( struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + +#ifdef CONFIG_SBNI_MULTILINE + nl->master->stats.tx_packets++; + nl->master->stats.tx_bytes += nl->tx_buf_p->len; +#else + dev->stats.tx_packets++; + dev->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 = netdev_priv(dev); + + 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( dev ), +#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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + + unsigned int len; + + /* nl->tx_buf_p == NULL here! */ + if( nl->tx_buf_p ) + netdev_err(dev, "memory leak!\n"); + + 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 = DIV_ROUND_UP(len, 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 + netif_trans_update(nl->master); +#else + netif_trans_update(dev); +#endif +} + + +static void +drop_xmit_queue( struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + + if( nl->tx_buf_p ) + dev_kfree_skb_any( nl->tx_buf_p ), + nl->tx_buf_p = NULL, +#ifdef CONFIG_SBNI_MULTILINE + nl->master->stats.tx_errors++, + nl->master->stats.tx_carrier_errors++; +#else + dev->stats.tx_errors++, + dev->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 ); + netif_trans_update(nl->master); +#else + netif_start_queue( dev ); + netif_trans_update(dev); +#endif +} + + +static void +send_frame_header( struct net_device *dev, u32 *crc_p ) +{ + struct net_local *nl = netdev_priv(dev); + + 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; + + skb_reserve( skb, 2 ); /* Align IP on longword boundaries */ + return skb; +} + + +static void +indicate_pkt( struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + 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 ); + ++nl->master->stats.rx_packets; + nl->master->stats.rx_bytes += nl->inppos; +#else + skb->protocol = eth_type_trans( skb, dev ); + netif_rx( skb ); + ++dev->stats.rx_packets; + dev->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(struct timer_list *t) +{ + struct net_local *nl = from_timer(nl, t, watchdog); + struct net_device *dev = nl->watchdog_dev; + 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 ); + + mod_timer(t, jiffies + SBNI_TIMEOUT); + + 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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + 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 *) (netdev_priv(*p))) + ->second = dev; + netdev_notice(dev, "using shared irq with %s\n", + (*p)->name); + nl->state |= FL_SECONDARY; + goto handler_attached; + } + } + + if( request_irq(dev->irq, sbni_interrupt, IRQF_SHARED, dev->name, dev) ) { + netdev_err(dev, "unable to get IRQ %d\n", dev->irq); + return -EAGAIN; + } + +handler_attached: + + spin_lock( &nl->lock ); + memset( &dev->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 */ + nl->watchdog_dev = dev; + timer_setup(w, sbni_watchdog, 0); + w->expires = jiffies + SBNI_TIMEOUT; + add_timer( w ); + + spin_unlock( &nl->lock ); + return 0; +} + + +static int +sbni_close( struct net_device *dev ) +{ + struct net_local *nl = netdev_priv(dev); + + if( nl->second && nl->second->flags & IFF_UP ) { + netdev_notice(dev, "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 = netdev_priv(dev); + 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 (!capable(CAP_NET_ADMIN)) + 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 (!capable(CAP_NET_ADMIN)) + 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 (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (copy_from_user( slave_name, ifr->ifr_data, sizeof slave_name )) + return -EFAULT; + slave_dev = dev_get_by_name(&init_net, slave_name ); + if( !slave_dev || !(slave_dev->flags & IFF_UP) ) { + netdev_err(dev, "trying to enslave non-active device %s\n", + slave_name); + if (slave_dev) + dev_put(slave_dev); + return -EPERM; + } + + return enslave( dev, slave_dev ); + + case SIOCDEVEMANSIPATE : + if (!capable(CAP_NET_ADMIN)) + 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 = netdev_priv(dev); + struct net_local *snl = netdev_priv(slave_dev); + + 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( &slave_dev->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 ); + netdev_notice(dev, "slave device (%s) attached\n", slave_dev->name); + return 0; +} + + +static int +emancipate( struct net_device *dev ) +{ + struct net_local *snl = netdev_priv(dev); + struct net_device *p = snl->master; + struct net_local *nl = netdev_priv(p); + + 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 = netdev_priv(p); + 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 void +set_multicast_list( struct net_device *dev ) +{ + return; /* sbni always operate in promiscuos mode */ +} + + +#ifdef MODULE +module_param_hw_array(io, int, ioport, NULL, 0); +module_param_hw_array(irq, int, irq, 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 init_module( void ) +{ + struct net_device *dev; + int err; + + while( num < SBNI_MAX_NUM_CARDS ) { + dev = alloc_netdev(sizeof(struct net_local), "sbni%d", + NET_NAME_UNKNOWN, 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) +{ + int i; + + for (i = 0; i < SBNI_MAX_NUM_CARDS; ++i) { + struct net_device *dev = sbni_cards[i]; + if (dev != 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: + pr_err("Error in sbni kernel parameter!\n"); + return 0; +} + +__setup( "sbni=", sbni_setup ); + +#endif /* MODULE */ + +/* -------------------------------------------------------------------------- */ + +static u32 +calc_crc32( u32 crc, u8 *p, u32 len ) +{ + while( len-- ) + crc = CRC32( *p++, crc ); + + return 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 000000000..84264510a --- /dev/null +++ b/drivers/net/wan/sbni.h @@ -0,0 +1,147 @@ +/* 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 { +#ifdef __LITTLE_ENDIAN_BITFIELD + u8 rxl : 5; + u8 rate : 2; + u8 : 1; +#else + u8 : 1; + u8 rate : 2; + u8 rxl : 5; +#endif +}; + +/* 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 000000000..09fde60a5 --- /dev/null +++ b/drivers/net/wan/sdla.c @@ -0,0 +1,1658 @@ +/* + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/io.h> +#include <asm/dma.h> +#include <linux/uaccess.h> + +static const char* version = "SDLA driver v0.30, 12 Sep 1996, mike.mclagan@linux.org"; + +static unsigned int valid_port[] = { 0x250, 0x270, 0x280, 0x300, 0x350, 0x360, 0x380, 0x390}; + +static unsigned int valid_mem[] = { + 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; +} + +static void sdla_stop(struct net_device *dev) +{ + struct frad_local *flp; + + flp = netdev_priv(dev); + 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; + } +} + +static void sdla_start(struct net_device *dev) +{ + struct frad_local *flp; + + flp = netdev_priv(dev); + 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 + * + ***************************************************/ + +static 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; + char flags; +} __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) + netdev_info(dev, "Modem DCD unexpectedly low!\n"); + if (*state & SDLA_MODEM_CTS_LOW) + netdev_info(dev, "Modem CTS unexpectedly low!\n"); + /* I should probably do something about this! */ + break; + + case SDLA_RET_CHANNEL_OFF: + netdev_info(dev, "Channel became inoperative!\n"); + /* same here */ + break; + + case SDLA_RET_CHANNEL_ON: + netdev_info(dev, "Channel became operative!\n"); + /* same here */ + break; + + case SDLA_RET_DLCI_STATUS: + netdev_info(dev, "Status change reported by Access Node\n"); + 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; + } + netdev_info(dev, "DLCI %i: %s\n", + pstatus->dlci, state); + /* same here */ + } + break; + + case SDLA_RET_DLCI_UNKNOWN: + netdev_info(dev, "Received unknown DLCIs:"); + len /= sizeof(short); + for(pdlci = data,i=0;i < len;i++,pdlci++) + pr_cont(" %i", *pdlci); + pr_cont("\n"); + break; + + case SDLA_RET_TIMEOUT: + netdev_err(dev, "Command timed out!\n"); + break; + + case SDLA_RET_BUF_OVERSIZE: + netdev_info(dev, "Bc/CIR overflow, acceptable size is %i\n", + len); + break; + + case SDLA_RET_BUF_TOO_BIG: + netdev_info(dev, "Buffer size over specified max of %i\n", + 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: + netdev_dbg(dev, "Cmd 0x%02X generated return code 0x%02X\n", + 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 = netdev_priv(dev); + 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); + +static int sdla_activate(struct net_device *slave, struct net_device *master) +{ + struct frad_local *flp; + int i; + + flp = netdev_priv(slave); + + 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; +} + +static int sdla_deactivate(struct net_device *slave, struct net_device *master) +{ + struct frad_local *flp; + int i; + + flp = netdev_priv(slave); + + 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; +} + +static 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 = netdev_priv(slave); + + 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; +} + +static int sdla_deassoc(struct net_device *slave, struct net_device *master) +{ + struct frad_local *flp; + int i; + + flp = netdev_priv(slave); + + 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; +} + +static 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 = netdev_priv(slave); + + for(i=0;i<CONFIG_DLCI_MAX;i++) + if (flp->master[i] == master) + break; + + if (i == CONFIG_DLCI_MAX) + return -ENODEV; + + dlp = netdev_priv(master); + + 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 netdev_tx_t 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 = netdev_priv(dev); + 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) + { + netdev_warn(dev, "Non DLCI device, type %i, tried to send on FRAD module\n", + skb->dev->type); + accept = 0; + } + break; + default: + netdev_warn(dev, "unknown firmware type 0x%04X\n", + 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 *)(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: + dev->stats.tx_packets++; + break; + + case SDLA_RET_CIR_OVERFLOW: + case SDLA_RET_BUF_OVERSIZE: + case SDLA_RET_NO_BUFS: + dev->stats.tx_dropped++; + break; + + default: + dev->stats.tx_errors++; + break; + } + } + netif_wake_queue(dev); + for(i=0;i<CONFIG_DLCI_MAX;i++) + { + if(flp->master[i]!=NULL) + netif_wake_queue(flp->master[i]); + } + + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +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 = netdev_priv(dev); + 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) + { + netdev_notice(dev, "Received packet from invalid DLCI %i, ignoring\n", + dlci); + dev->stats.rx_errors++; + success = 0; + } + } + + if (success) + { + master = flp->master[i]; + skb = dev_alloc_skb(len + sizeof(struct frhdr)); + if (skb == NULL) + { + netdev_notice(dev, "Memory squeeze, dropping packet\n"); + dev->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) + { + dev->stats.rx_packets++; + dlp = netdev_priv(master); + (*dlp->receive)(skb, master); + } + + spin_unlock_irqrestore(&sdla_lock, flags); +} + +static irqreturn_t sdla_isr(int dummy, void *dev_id) +{ + struct net_device *dev; + struct frad_local *flp; + char byte; + + dev = dev_id; + + flp = netdev_priv(dev); + + if (!flp->initialized) + { + netdev_warn(dev, "irq %d for uninitialized device\n", dev->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: + netdev_warn(dev, "invalid irq flag 0x%02X\n", 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(struct timer_list *t) +{ + struct frad_local *flp = from_timer(flp, t, timer); + struct net_device *dev = flp->dev; + + 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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + + 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 = netdev_priv(flp->master[i]); + 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 = netdev_priv(dev); + + 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 = kzalloc(mem.len, GFP_KERNEL); + if (!temp) + return -ENOMEM; + 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 = memdup_user(mem.data, mem.len); + if (IS_ERR(temp)) + return PTR_ERR(temp); + 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 = netdev_priv(dev); + + 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 = netdev_priv(dev); + + 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; +} + +static int sdla_change_mtu(struct net_device *dev, int new_mtu) +{ + if (netif_running(dev)) + return -EBUSY; + + /* for now, you can't change the MTU! */ + return -EOPNOTSUPP; +} + +static 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 = netdev_priv(dev); + + if (flp->initialized) + return -EINVAL; + + for(i=0; i < ARRAY_SIZE(valid_port); i++) + if (valid_port[i] == map->base_addr) + break; + + if (i == ARRAY_SIZE(valid_port)) + return -EINVAL; + + if (!request_region(map->base_addr, SDLA_IO_EXTENTS, dev->name)){ + pr_warn("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; + } + } + } + + netdev_notice(dev, "Unknown card type\n"); + 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 < ARRAY_SIZE(valid_mem); i++) + if (valid_mem[i] == map->mem_start) + break; + + err = -EINVAL; + if (i == ARRAY_SIZE(valid_mem)) + 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 const struct net_device_ops sdla_netdev_ops = { + .ndo_open = sdla_open, + .ndo_stop = sdla_close, + .ndo_do_ioctl = sdla_ioctl, + .ndo_set_config = sdla_set_config, + .ndo_start_xmit = sdla_transmit, + .ndo_change_mtu = sdla_change_mtu, +}; + +static void setup_sdla(struct net_device *dev) +{ + struct frad_local *flp = netdev_priv(dev); + + netdev_boot_setup_check(dev); + + dev->netdev_ops = &sdla_netdev_ops; + dev->flags = 0; + dev->type = 0xFFFF; + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->mtu = SDLA_MAX_MTU; + + flp->activate = sdla_activate; + flp->deactivate = sdla_deactivate; + flp->assoc = sdla_assoc; + flp->deassoc = sdla_deassoc; + flp->dlci_conf = sdla_dlci_conf; + flp->dev = dev; + + timer_setup(&flp->timer, sdla_poll, 0); + flp->timer.expires = 1; +} + +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", + NET_NAME_UNKNOWN, 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 = netdev_priv(sdla); + + 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/sealevel.c b/drivers/net/wan/sealevel.c new file mode 100644 index 000000000..c56f2c252 --- /dev/null +++ b/drivers/net/wan/sealevel.c @@ -0,0 +1,397 @@ +/* + * 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. + * Generic HDLC port Copyright (C) 2008 Krzysztof Halasa <khc@pm.waw.pl> + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/hdlc.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <net/arp.h> + +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> +#include "z85230.h" + + +struct slvl_device +{ + struct z8530_channel *chan; + int channel; +}; + + +struct slvl_board +{ + struct slvl_device dev[2]; + struct z8530_dev board; + int iobase; +}; + +/* + * Network driver support routines + */ + +static inline struct slvl_device* dev_to_chan(struct net_device *dev) +{ + return (struct slvl_device *)dev_to_hdlc(dev)->priv; +} + +/* + * Frame receive. Simple for our card as we do HDLC 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 = hdlc_type_trans(skb, c->netdevice); + skb_reset_mac_header(skb); + skb->dev = c->netdevice; + netif_rx(skb); +} + +/* + * We've been placed in the UP state + */ + +static int sealevel_open(struct net_device *d) +{ + struct slvl_device *slvl = dev_to_chan(d); + 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; + + err = hdlc_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 = dev_to_chan(d); + int unit = slvl->channel; + + /* + * Discard new frames + */ + + slvl->chan->rx_function = z8530_null_rx; + + hdlc_close(d); + 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=dev_to_chan(d); + z8530_ioctl(d,&slvl->sync.chanA,ifr,cmd) */ + return hdlc_ioctl(d, ifr, cmd); +} + +/* + * Passed network frames, fire them downwind. + */ + +static netdev_tx_t sealevel_queue_xmit(struct sk_buff *skb, + struct net_device *d) +{ + return z8530_queue_xmit(dev_to_chan(d)->chan, skb); +} + +static int sealevel_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + if (encoding == ENCODING_NRZ && parity == PARITY_CRC16_PR1_CCITT) + return 0; + return -EINVAL; +} + +static const struct net_device_ops sealevel_ops = { + .ndo_open = sealevel_open, + .ndo_stop = sealevel_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = sealevel_ioctl, +}; + +static int slvl_setup(struct slvl_device *sv, int iobase, int irq) +{ + struct net_device *dev = alloc_hdlcdev(sv); + if (!dev) + return -1; + + dev_to_hdlc(dev)->attach = sealevel_attach; + dev_to_hdlc(dev)->xmit = sealevel_queue_xmit; + dev->netdev_ops = &sealevel_ops; + dev->base_addr = iobase; + dev->irq = irq; + + if (register_hdlc_device(dev)) { + pr_err("unable to register HDLC device\n"); + free_netdev(dev); + return -1; + } + + sv->chan->netdevice = dev; + return 0; +} + + +/* + * 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")) { + pr_warn("I/O 0x%X already in use\n", iobase); + return NULL; + } + + b = kzalloc(sizeof(struct slvl_board), GFP_KERNEL); + if (!b) + goto err_kzalloc; + + b->dev[0].chan = &b->board.chanA; + b->dev[0].channel = 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, 0, + "SeaLevel", dev) < 0) { + pr_warn("IRQ %d already in use\n", irq); + goto err_request_irq; + } + + dev->irq = irq; + dev->chanA.private = &b->dev[0]; + dev->chanB.private = &b->dev[1]; + dev->chanA.dev = dev; + dev->chanB.dev = dev; + + dev->chanA.txdma = 3; + dev->chanA.rxdma = 1; + if (request_dma(dev->chanA.txdma, "SeaLevel (TX)")) + goto err_dma_tx; + + if (request_dma(dev->chanA.rxdma, "SeaLevel (RX)")) + goto err_dma_rx; + + disable_irq(irq); + + /* + * Begin normal initialise + */ + + if (z8530_init(dev) != 0) { + pr_err("Z8530 series device not found\n"); + enable_irq(irq); + goto free_hw; + } + 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 (slvl_setup(&b->dev[0], iobase, irq)) + goto free_hw; + if (slvl_setup(&b->dev[1], iobase, irq)) + goto free_netdev0; + + z8530_describe(dev, "I/O", iobase); + dev->active = 1; + return b; + +free_netdev0: + unregister_hdlc_device(b->dev[0].chan->netdevice); + free_netdev(b->dev[0].chan->netdevice); +free_hw: + free_dma(dev->chanA.rxdma); +err_dma_rx: + free_dma(dev->chanA.txdma); +err_dma_tx: + free_irq(irq, dev); +err_request_irq: + kfree(b); +err_kzalloc: + 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].chan->netdevice; + unregister_hdlc_device(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 bool slow=false; + +module_param_hw(io, int, ioport, 0); +MODULE_PARM_DESC(io, "The I/O base of the Sealevel card"); +module_param_hw(txdma, int, dma, 0); +MODULE_PARM_DESC(txdma, "Transmit DMA channel"); +module_param_hw(rxdma, int, dma, 0); +MODULE_PARM_DESC(rxdma, "Receive DMA channel"); +module_param_hw(irq, int, irq, 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) +{ + 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/slic_ds26522.c b/drivers/net/wan/slic_ds26522.c new file mode 100644 index 000000000..1f6bc8791 --- /dev/null +++ b/drivers/net/wan/slic_ds26522.c @@ -0,0 +1,251 @@ +/* + * drivers/net/wan/slic_ds26522.c + * + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * + * Author:Zhao Qiang<qiang.zhao@nxp.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. + */ + +#include <linux/bitrev.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/spi/spi.h> +#include <linux/wait.h> +#include <linux/param.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include "slic_ds26522.h" + +#define DRV_NAME "ds26522" + +#define SLIC_TRANS_LEN 1 +#define SLIC_TWO_LEN 2 +#define SLIC_THREE_LEN 3 + +static struct spi_device *g_spi; + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Zhao Qiang<B45475@freescale.com>"); + +/* the read/write format of address is + * w/r|A13|A12|A11|A10|A9|A8|A7|A6|A5|A4|A3|A2|A1|A0|x + */ +static void slic_write(struct spi_device *spi, u16 addr, + u8 data) +{ + u8 temp[3]; + + addr = bitrev16(addr) >> 1; + data = bitrev8(data); + temp[0] = (u8)((addr >> 8) & 0x7f); + temp[1] = (u8)(addr & 0xfe); + temp[2] = data; + + /* write spi addr and value */ + spi_write(spi, &temp[0], SLIC_THREE_LEN); +} + +static u8 slic_read(struct spi_device *spi, u16 addr) +{ + u8 temp[2]; + u8 data; + + addr = bitrev16(addr) >> 1; + temp[0] = (u8)(((addr >> 8) & 0x7f) | 0x80); + temp[1] = (u8)(addr & 0xfe); + + spi_write_then_read(spi, &temp[0], SLIC_TWO_LEN, &data, + SLIC_TRANS_LEN); + + data = bitrev8(data); + return data; +} + +static bool get_slic_product_code(struct spi_device *spi) +{ + u8 device_id; + + device_id = slic_read(spi, DS26522_IDR_ADDR); + if ((device_id & 0xf8) == 0x68) + return true; + else + return false; +} + +static void ds26522_e1_spec_config(struct spi_device *spi) +{ + /* Receive E1 Mode, Framer Disabled */ + slic_write(spi, DS26522_RMMR_ADDR, DS26522_RMMR_E1); + + /* Transmit E1 Mode, Framer Disable */ + slic_write(spi, DS26522_TMMR_ADDR, DS26522_TMMR_E1); + + /* Receive E1 Mode Framer Enable */ + slic_write(spi, DS26522_RMMR_ADDR, + slic_read(spi, DS26522_RMMR_ADDR) | DS26522_RMMR_FRM_EN); + + /* Transmit E1 Mode Framer Enable */ + slic_write(spi, DS26522_TMMR_ADDR, + slic_read(spi, DS26522_TMMR_ADDR) | DS26522_TMMR_FRM_EN); + + /* RCR1, receive E1 B8zs & ESF */ + slic_write(spi, DS26522_RCR1_ADDR, + DS26522_RCR1_E1_HDB3 | DS26522_RCR1_E1_CCS); + + /* RSYSCLK=2.048MHz, RSYNC-Output */ + slic_write(spi, DS26522_RIOCR_ADDR, + DS26522_RIOCR_2048KHZ | DS26522_RIOCR_RSIO_OUT); + + /* TCR1 Transmit E1 b8zs */ + slic_write(spi, DS26522_TCR1_ADDR, DS26522_TCR1_TB8ZS); + + /* TSYSCLK=2.048MHz, TSYNC-Output */ + slic_write(spi, DS26522_TIOCR_ADDR, + DS26522_TIOCR_2048KHZ | DS26522_TIOCR_TSIO_OUT); + + /* Set E1TAF */ + slic_write(spi, DS26522_E1TAF_ADDR, DS26522_E1TAF_DEFAULT); + + /* Set E1TNAF register */ + slic_write(spi, DS26522_E1TNAF_ADDR, DS26522_E1TNAF_DEFAULT); + + /* Receive E1 Mode Framer Enable & init Done */ + slic_write(spi, DS26522_RMMR_ADDR, slic_read(spi, DS26522_RMMR_ADDR) | + DS26522_RMMR_INIT_DONE); + + /* Transmit E1 Mode Framer Enable & init Done */ + slic_write(spi, DS26522_TMMR_ADDR, slic_read(spi, DS26522_TMMR_ADDR) | + DS26522_TMMR_INIT_DONE); + + /* Configure LIU E1 mode */ + slic_write(spi, DS26522_LTRCR_ADDR, DS26522_LTRCR_E1); + + /* E1 Mode default 75 ohm w/Transmit Impedance Matlinking */ + slic_write(spi, DS26522_LTITSR_ADDR, + DS26522_LTITSR_TLIS_75OHM | DS26522_LTITSR_LBOS_75OHM); + + /* E1 Mode default 75 ohm Long Haul w/Receive Impedance Matlinking */ + slic_write(spi, DS26522_LRISMR_ADDR, + DS26522_LRISMR_75OHM | DS26522_LRISMR_MAX); + + /* Enable Transmit output */ + slic_write(spi, DS26522_LMCR_ADDR, DS26522_LMCR_TE); +} + +static int slic_ds26522_init_configure(struct spi_device *spi) +{ + u16 addr; + + /* set clock */ + slic_write(spi, DS26522_GTCCR_ADDR, DS26522_GTCCR_BPREFSEL_REFCLKIN | + DS26522_GTCCR_BFREQSEL_2048KHZ | + DS26522_GTCCR_FREQSEL_2048KHZ); + slic_write(spi, DS26522_GTCR2_ADDR, DS26522_GTCR2_TSSYNCOUT); + slic_write(spi, DS26522_GFCR_ADDR, DS26522_GFCR_BPCLK_2048KHZ); + + /* set gtcr */ + slic_write(spi, DS26522_GTCR1_ADDR, DS26522_GTCR1); + + /* Global LIU Software Reset Register */ + slic_write(spi, DS26522_GLSRR_ADDR, DS26522_GLSRR_RESET); + + /* Global Framer and BERT Software Reset Register */ + slic_write(spi, DS26522_GFSRR_ADDR, DS26522_GFSRR_RESET); + + usleep_range(100, 120); + + slic_write(spi, DS26522_GLSRR_ADDR, DS26522_GLSRR_NORMAL); + slic_write(spi, DS26522_GFSRR_ADDR, DS26522_GFSRR_NORMAL); + + /* Perform RX/TX SRESET,Reset receiver */ + slic_write(spi, DS26522_RMMR_ADDR, DS26522_RMMR_SFTRST); + + /* Reset tranceiver */ + slic_write(spi, DS26522_TMMR_ADDR, DS26522_TMMR_SFTRST); + + usleep_range(100, 120); + + /* Zero all Framer Registers */ + for (addr = DS26522_RF_ADDR_START; addr <= DS26522_RF_ADDR_END; + addr++) + slic_write(spi, addr, 0); + + for (addr = DS26522_TF_ADDR_START; addr <= DS26522_TF_ADDR_END; + addr++) + slic_write(spi, addr, 0); + + for (addr = DS26522_LIU_ADDR_START; addr <= DS26522_LIU_ADDR_END; + addr++) + slic_write(spi, addr, 0); + + for (addr = DS26522_BERT_ADDR_START; addr <= DS26522_BERT_ADDR_END; + addr++) + slic_write(spi, addr, 0); + + /* setup ds26522 for E1 specification */ + ds26522_e1_spec_config(spi); + + slic_write(spi, DS26522_GTCR1_ADDR, 0x00); + + return 0; +} + +static int slic_ds26522_remove(struct spi_device *spi) +{ + pr_info("DS26522 module uninstalled\n"); + return 0; +} + +static int slic_ds26522_probe(struct spi_device *spi) +{ + int ret = 0; + + g_spi = spi; + spi->bits_per_word = 8; + + if (!get_slic_product_code(spi)) + return ret; + + ret = slic_ds26522_init_configure(spi); + if (ret == 0) + pr_info("DS26522 cs%d configured\n", spi->chip_select); + + return ret; +} + +static const struct spi_device_id slic_ds26522_id[] = { + { .name = "ds26522" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(spi, slic_ds26522_id); + +static const struct of_device_id slic_ds26522_match[] = { + { + .compatible = "maxim,ds26522", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, slic_ds26522_match); + +static struct spi_driver slic_ds26522_driver = { + .driver = { + .name = "ds26522", + .bus = &spi_bus_type, + .of_match_table = slic_ds26522_match, + }, + .probe = slic_ds26522_probe, + .remove = slic_ds26522_remove, + .id_table = slic_ds26522_id, +}; + +module_spi_driver(slic_ds26522_driver); diff --git a/drivers/net/wan/slic_ds26522.h b/drivers/net/wan/slic_ds26522.h new file mode 100644 index 000000000..22aa0ecbd --- /dev/null +++ b/drivers/net/wan/slic_ds26522.h @@ -0,0 +1,134 @@ +/* + * drivers/tdm/line_ctrl/slic_ds26522.h + * + * Copyright 2016 Freescale Semiconductor, Inc. + * + * Author: Zhao Qiang <B45475@freescale.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. + */ + +#define DS26522_RF_ADDR_START 0x00 +#define DS26522_RF_ADDR_END 0xef +#define DS26522_GLB_ADDR_START 0xf0 +#define DS26522_GLB_ADDR_END 0xff +#define DS26522_TF_ADDR_START 0x100 +#define DS26522_TF_ADDR_END 0x1ef +#define DS26522_LIU_ADDR_START 0x1000 +#define DS26522_LIU_ADDR_END 0x101f +#define DS26522_TEST_ADDR_START 0x1008 +#define DS26522_TEST_ADDR_END 0x101f +#define DS26522_BERT_ADDR_START 0x1100 +#define DS26522_BERT_ADDR_END 0x110f + +#define DS26522_RMMR_ADDR 0x80 +#define DS26522_RCR1_ADDR 0x81 +#define DS26522_RCR3_ADDR 0x83 +#define DS26522_RIOCR_ADDR 0x84 + +#define DS26522_GTCR1_ADDR 0xf0 +#define DS26522_GFCR_ADDR 0xf1 +#define DS26522_GTCR2_ADDR 0xf2 +#define DS26522_GTCCR_ADDR 0xf3 +#define DS26522_GLSRR_ADDR 0xf5 +#define DS26522_GFSRR_ADDR 0xf6 +#define DS26522_IDR_ADDR 0xf8 + +#define DS26522_E1TAF_ADDR 0x164 +#define DS26522_E1TNAF_ADDR 0x165 +#define DS26522_TMMR_ADDR 0x180 +#define DS26522_TCR1_ADDR 0x181 +#define DS26522_TIOCR_ADDR 0x184 + +#define DS26522_LTRCR_ADDR 0x1000 +#define DS26522_LTITSR_ADDR 0x1001 +#define DS26522_LMCR_ADDR 0x1002 +#define DS26522_LRISMR_ADDR 0x1007 + +#define MAX_NUM_OF_CHANNELS 8 +#define PQ_MDS_8E1T1_BRD_REV 0x00 +#define PQ_MDS_8E1T1_PLD_REV 0x00 + +#define DS26522_GTCCR_BPREFSEL_REFCLKIN 0xa0 +#define DS26522_GTCCR_BFREQSEL_1544KHZ 0x08 +#define DS26522_GTCCR_FREQSEL_1544KHZ 0x04 +#define DS26522_GTCCR_BFREQSEL_2048KHZ 0x00 +#define DS26522_GTCCR_FREQSEL_2048KHZ 0x00 + +#define DS26522_GFCR_BPCLK_2048KHZ 0x00 + +#define DS26522_GTCR2_TSSYNCOUT 0x02 +#define DS26522_GTCR1 0x00 + +#define DS26522_GFSRR_RESET 0x01 +#define DS26522_GFSRR_NORMAL 0x00 + +#define DS26522_GLSRR_RESET 0x01 +#define DS26522_GLSRR_NORMAL 0x00 + +#define DS26522_RMMR_SFTRST 0x02 +#define DS26522_RMMR_FRM_EN 0x80 +#define DS26522_RMMR_INIT_DONE 0x40 +#define DS26522_RMMR_T1 0x00 +#define DS26522_RMMR_E1 0x01 + +#define DS26522_E1TAF_DEFAULT 0x1b +#define DS26522_E1TNAF_DEFAULT 0x40 + +#define DS26522_TMMR_SFTRST 0x02 +#define DS26522_TMMR_FRM_EN 0x80 +#define DS26522_TMMR_INIT_DONE 0x40 +#define DS26522_TMMR_T1 0x00 +#define DS26522_TMMR_E1 0x01 + +#define DS26522_RCR1_T1_SYNCT 0x80 +#define DS26522_RCR1_T1_RB8ZS 0x40 +#define DS26522_RCR1_T1_SYNCC 0x08 + +#define DS26522_RCR1_E1_HDB3 0x40 +#define DS26522_RCR1_E1_CCS 0x20 + +#define DS26522_RIOCR_1544KHZ 0x00 +#define DS26522_RIOCR_2048KHZ 0x10 +#define DS26522_RIOCR_RSIO_OUT 0x00 + +#define DS26522_RCR3_FLB 0x01 + +#define DS26522_TIOCR_1544KHZ 0x00 +#define DS26522_TIOCR_2048KHZ 0x10 +#define DS26522_TIOCR_TSIO_OUT 0x04 + +#define DS26522_TCR1_TB8ZS 0x04 + +#define DS26522_LTRCR_T1 0x02 +#define DS26522_LTRCR_E1 0x00 + +#define DS26522_LTITSR_TLIS_75OHM 0x00 +#define DS26522_LTITSR_LBOS_75OHM 0x00 +#define DS26522_LTITSR_TLIS_100OHM 0x10 +#define DS26522_LTITSR_TLIS_0DB_CSU 0x00 + +#define DS26522_LRISMR_75OHM 0x00 +#define DS26522_LRISMR_100OHM 0x10 +#define DS26522_LRISMR_MAX 0x03 + +#define DS26522_LMCR_TE 0x01 + +enum line_rate { + LINE_RATE_T1, /* T1 line rate (1.544 Mbps) */ + LINE_RATE_E1 /* E1 line rate (2.048 Mbps) */ +}; + +enum tdm_trans_mode { + NORMAL = 0, + FRAMER_LB +}; + +enum card_support_type { + LM_CARD = 0, + DS26522_CARD, + NO_CARD +}; diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c new file mode 100644 index 000000000..d573a57bc --- /dev/null +++ b/drivers/net/wan/wanxl.c @@ -0,0 +1,849 @@ +/* + * 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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/interrupt.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/hdlc.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <asm/io.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 */ + + +struct port { + struct net_device *dev; + struct card *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]; +}; + + +struct card_status { + desc_t rx_descs[RX_QUEUE_LENGTH]; + port_status_t port_status[4]; +}; + + +struct card { + 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]; + struct card_status *status; /* shared between host and card */ + dma_addr_t status_address; + struct port ports[0]; /* 1 - 4 port structures follow */ +}; + + + +static inline struct port *dev_to_port(struct net_device *dev) +{ + return (struct port *)dev_to_hdlc(dev)->priv; +} + + +static inline port_status_t *get_status(struct port *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) + pr_crit("%s: pci_map_single() returned memory at 0x%llx!\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(struct port *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"; + } + netdev_info(port->dev, "%s%s module, %s cable%s%s\n", + pm, dte, cable, dsr, dcd); + + if (value & STATUS_CABLE_DCD) + netif_carrier_on(port->dev); + else + netif_carrier_off(port->dev); +} + + + +/* Transmit complete interrupt service */ +static inline void wanxl_tx_intr(struct port *port) +{ + struct net_device *dev = port->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: + dev->stats.tx_errors++; + dev->stats.tx_fifo_errors++; + break; + + default: + dev->stats.tx_packets++; + dev->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(struct card *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) + pr_crit("%s: received packet for nonexistent port\n", + pci_name(card->pdev)); + else { + struct sk_buff *skb = card->rx_skbs[card->rx_in]; + struct port *port = &card->ports[desc->stat & + PACKET_PORT_MASK]; + struct net_device *dev = port->dev; + + if (!skb) + dev->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 + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + 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 card *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 netdev_tx_t wanxl_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct port *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(&port->lock); + return NETDEV_TX_BUSY; /* 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); + + 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 NETDEV_TX_OK; +} + + + +static int wanxl_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ + struct port *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; + struct port *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; + } + memset(&line, 0, sizeof(line)); + 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) +{ + struct port *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) { + netdev_err(dev, "port already open\n"); + 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)); + + netdev_err(dev, "unable to open port\n"); + /* 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) +{ + struct port *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) + netdev_err(dev, "unable to close port\n"); + + 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 port *port = dev_to_port(dev); + + dev->stats.rx_over_errors = get_status(port)->rx_overruns; + dev->stats.rx_frame_errors = get_status(port)->rx_frame_errors; + dev->stats.rx_errors = dev->stats.rx_over_errors + + dev->stats.rx_frame_errors; + return &dev->stats; +} + + + +static int wanxl_puts_command(struct card *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(struct card *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) +{ + struct card *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(struct card_status), + card->status, card->status_address); + + pci_release_regions(pdev); + pci_disable_device(pdev); + kfree(card); +} + + +#include "wanxlfw.inc" + +static const struct net_device_ops wanxl_ops = { + .ndo_open = wanxl_open, + .ndo_stop = wanxl_close, + .ndo_start_xmit = hdlc_start_xmit, + .ndo_do_ioctl = wanxl_ioctl, + .ndo_get_stats = wanxl_get_stats, +}; + +static int wanxl_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct card *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 + pr_info_once("%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, DMA_BIT_MASK(28)) || + pci_set_dma_mask(pdev, DMA_BIT_MASK(28))) { + pr_err("No usable DMA configuration\n"); + pci_disable_device(pdev); + 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(struct card) + ports * sizeof(struct port); + card = kzalloc(alloc_size, GFP_KERNEL); + if (card == NULL) { + pci_release_regions(pdev); + pci_disable_device(pdev); + return -ENOBUFS; + } + + pci_set_drvdata(pdev, card); + card->pdev = pdev; + + card->status = pci_alloc_consistent(pdev, + sizeof(struct card_status), + &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, DMA_BIT_MASK(32)) || + pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { + pr_err("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 (!card->plx) { + pr_err("ioremap() failed\n"); + wanxl_pci_remove_one(pdev); + return -EFAULT; + } + +#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)) { + pr_warn("%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: + pr_warn("%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) { + pr_warn("%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)) { + pr_warn("%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)); + if (!mem) { + pr_err("ioremap() failed\n"); + wanxl_pci_remove_one(pdev); + return -EFAULT; + } + + for (i = 0; i < sizeof(firmware); i += 4) + writel(ntohl(*(__be32*)(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)) { + pr_warn("%s: unable to Abort and Jump\n", pci_name(pdev)); + wanxl_pci_remove_one(pdev); + return -ENODEV; + } + + timeout = jiffies + 5 * HZ; + do { + if ((stat = readl(card->plx + PLX_MAILBOX_5)) != 0) + break; + schedule(); + }while (time_after(timeout, jiffies)); + + if (!stat) { + pr_warn("%s: timeout while initializing card firmware\n", + pci_name(pdev)); + wanxl_pci_remove_one(pdev); + return -ENODEV; + } + +#if DETECT_RAM + ramsize = stat; +#endif + + pr_info("%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, IRQF_SHARED, "wanXL", card)) { + pr_warn("%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; + struct port *port = &card->ports[i]; + struct net_device *dev = alloc_hdlcdev(port); + if (!dev) { + pr_err("%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); + dev->tx_queue_len = 50; + dev->netdev_ops = &wanxl_ops; + hdlc->attach = wanxl_attach; + hdlc->xmit = wanxl_xmit; + port->card = card; + port->node = i; + get_status(port)->clocking = CLOCK_EXT; + if (register_hdlc_device(dev)) { + pr_err("%s: unable to register hdlc device\n", + pci_name(pdev)); + free_netdev(dev); + wanxl_pci_remove_one(pdev); + return -ENOBUFS; + } + card->n_ports++; + } + + pr_info("%s: port", pci_name(pdev)); + for (i = 0; i < ports; i++) + pr_cont("%s #%i: %s", + i ? "," : "", i, card->ports[i].dev->name); + pr_cont("\n"); + + for (i = 0; i < ports; i++) + wanxl_cable_intr(&card->ports[i]); /* get carrier status etc.*/ + + return 0; +} + +static const struct pci_device_id wanxl_pci_tbl[] = { + { 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 + pr_info("%s\n", version); +#endif + return pci_register_driver(&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 000000000..3f86558f8 --- /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 000000000..21565d59e --- /dev/null +++ b/drivers/net/wan/wanxlfw.S @@ -0,0 +1,896 @@ +.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 <linux/hdlc/ioctl.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 000000000..73da688f9 --- /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 000000000..6fe9695a5 --- /dev/null +++ b/drivers/net/wan/x25_asy.c @@ -0,0 +1,834 @@ +/* + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> + +#include <linux/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/lapb.h> +#include <linux/init.h> +#include <linux/rtnetlink.h> +#include <linux/compat.h> +#include <linux/slab.h> +#include <net/x25device.h> +#include "x25_asy.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 = netdev_priv(dev); + /* 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, + NET_NAME_UNKNOWN, x25_asy_setup); + if (!dev) + return NULL; + + /* Initialize channel control data */ + sl = netdev_priv(dev); + 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 { + pr_warn("%s(): register_netdev() failure\n", __func__); + 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. */ + kfree(sl->rbuff); + sl->rbuff = NULL; + kfree(sl->xbuff); + sl->xbuff = NULL; + + if (!test_and_clear_bit(SLF_INUSE, &sl->flags)) + netdev_err(sl->dev, "x25_asy_free for already free unit\n"); +} + +static int x25_asy_change_mtu(struct net_device *dev, int newmtu) +{ + struct x25_asy *sl = netdev_priv(dev); + unsigned char *xbuff, *rbuff; + int len; + + len = 2 * newmtu; + xbuff = kmalloc(len + 4, GFP_ATOMIC); + rbuff = kmalloc(len + 4, GFP_ATOMIC); + + if (xbuff == NULL || rbuff == NULL) { + kfree(xbuff); + 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; + dev->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; + dev->stats.rx_over_errors++; + set_bit(SLF_ERROR, &sl->flags); + } + } + + dev->mtu = newmtu; + sl->buffsize = len; + + spin_unlock_bh(&sl->lock); + + kfree(xbuff); + 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 an LAPB frame to the LAPB module to process. */ + +static void x25_asy_bump(struct x25_asy *sl) +{ + struct net_device *dev = sl->dev; + struct sk_buff *skb; + int count; + int err; + + count = sl->rcount; + dev->stats.rx_bytes += count; + + skb = dev_alloc_skb(count); + if (skb == NULL) { + netdev_warn(sl->dev, "memory squeeze, dropping packet\n"); + dev->stats.rx_dropped++; + return; + } + skb_put_data(skb, sl->rbuff, count); + skb->protocol = x25_type_trans(skb, sl->dev); + err = lapb_data_received(skb->dev, skb); + if (err != LAPB_OK) { + kfree_skb(skb); + printk(KERN_DEBUG "x25_asy: data received err - %d\n", err); + } else { + dev->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(KERN_DEBUG "%s: truncating oversized transmit packet!\n", + sl->dev->name); + sl->dev->stats.tx_dropped++; + x25_asy_unlock(sl); + return; + } + + p = icp; + count = x25_asy_esc(p, 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. + */ + set_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + actual = sl->tty->ops->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 = 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->dev->stats.tx_packets++; + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + x25_asy_unlock(sl); + return; + } + + actual = tty->ops->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 = netdev_priv(dev); + + spin_lock(&sl->lock); + if (netif_queue_stopped(dev)) { + /* May be we must check transmitter timeout here ? + * 14 Oct 1994 Dmitry Gorodchanin. + */ + netdev_warn(dev, "transmit timed out, %s?\n", + (tty_chars_in_buffer(sl->tty) || sl->xleft) ? + "bad line quality" : "driver error"); + sl->xleft = 0; + clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + x25_asy_unlock(sl); + } + spin_unlock(&sl->lock); +} + +/* Encapsulate an IP datagram and kick it into a TTY queue. */ + +static netdev_tx_t x25_asy_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct x25_asy *sl = netdev_priv(dev); + int err; + + if (!netif_running(sl->dev)) { + netdev_err(dev, "xmit call when iface is down\n"); + kfree_skb(skb); + return NETDEV_TX_OK; + } + + switch (skb->data[0]) { + case X25_IFACE_DATA: + break; + case X25_IFACE_CONNECT: /* Connection request .. do nothing */ + err = lapb_connect_request(dev); + if (err != LAPB_OK) + netdev_err(dev, "lapb_connect_request error: %d\n", + err); + kfree_skb(skb); + return NETDEV_TX_OK; + case X25_IFACE_DISCONNECT: /* do nothing - hang up ?? */ + err = lapb_disconnect_request(dev); + if (err != LAPB_OK) + netdev_err(dev, "lapb_disconnect_request error: %d\n", + err); + /* fall through */ + default: + kfree_skb(skb); + return NETDEV_TX_OK; + } + 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. + */ + + err = lapb_data_request(dev, skb); + if (err != LAPB_OK) { + netdev_err(dev, "lapb_data_request error: %d\n", err); + kfree_skb(skb); + return NETDEV_TX_OK; + } + return NETDEV_TX_OK; +} + + +/* + * LAPB interface boilerplate + */ + +/* + * Called when I frame data arrive. We add a pseudo header for upper + * layers and pass it to upper layers. + */ + +static int x25_asy_data_indication(struct net_device *dev, struct sk_buff *skb) +{ + if (skb_cow(skb, 1)) { + kfree_skb(skb); + return NET_RX_DROP; + } + skb_push(skb, 1); + skb->data[0] = X25_IFACE_DATA; + + skb->protocol = x25_type_trans(skb, dev); + + 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 = netdev_priv(dev); + + spin_lock(&sl->lock); + if (netif_queue_stopped(sl->dev) || sl->tty == NULL) { + spin_unlock(&sl->lock); + netdev_err(dev, "tbusy drop\n"); + kfree_skb(skb); + return; + } + /* We were not busy, so we are now... :-) */ + if (skb != NULL) { + x25_asy_lock(sl); + dev->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 = netdev_priv(dev); + struct sk_buff *skb; + unsigned char *ptr; + + skb = dev_alloc_skb(1); + if (skb == NULL) { + netdev_err(dev, "out of memory\n"); + return; + } + + ptr = skb_put(skb, 1); + *ptr = X25_IFACE_CONNECT; + + skb->protocol = x25_type_trans(skb, sl->dev); + netif_rx(skb); +} + +static void x25_asy_disconnected(struct net_device *dev, int reason) +{ + struct x25_asy *sl = netdev_priv(dev); + struct sk_buff *skb; + unsigned char *ptr; + + skb = dev_alloc_skb(1); + if (skb == NULL) { + netdev_err(dev, "out of memory\n"); + return; + } + + ptr = skb_put(skb, 1); + *ptr = X25_IFACE_DISCONNECT; + + skb->protocol = x25_type_trans(skb, sl->dev); + netif_rx(skb); +} + +static const 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 = netdev_priv(dev); + 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 = kmalloc(len + 4, GFP_KERNEL); + if (sl->rbuff == NULL) + goto norbuff; + sl->xbuff = 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 + */ + err = lapb_register(dev, &x25_asy_callbacks); + if (err == LAPB_OK) + return 0; + + /* Cleanup */ + kfree(sl->xbuff); + sl->xbuff = NULL; +noxbuff: + kfree(sl->rbuff); + sl->rbuff = NULL; +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 = netdev_priv(dev); + + spin_lock(&sl->lock); + if (sl->tty) + clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags); + + netif_stop_queue(dev); + sl->rcount = 0; + sl->xleft = 0; + spin_unlock(&sl->lock); + return 0; +} + +/* + * 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 = 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->dev->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; + int err; + + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + /* OK. Find a free X.25 channel to use. */ + sl = x25_asy_alloc(); + if (sl == NULL) + return -ENFILE; + + sl->tty = tty; + tty->disc_data = sl; + tty->receive_room = 65536; + tty_driver_flush_buffer(tty); + tty_ldisc_flush(tty); + + /* Restore default settings */ + sl->dev->type = ARPHRD_X25; + + /* Perform the low-level X.25 async init */ + err = x25_asy_open(sl->dev); + if (err) { + x25_asy_free(sl); + return err; + } + /* Done. We have linked the TTY line to a channel. */ + return 0; +} + + +/* + * 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 = tty->disc_data; + int err; + + /* First make sure we're connected. */ + if (!sl || sl->magic != X25_ASY_MAGIC) + return; + + rtnl_lock(); + if (sl->dev->flags & IFF_UP) + dev_close(sl->dev); + rtnl_unlock(); + + err = lapb_unregister(sl->dev); + if (err != LAPB_OK) + pr_err("x25_asy_close: lapb_unregister error: %d\n", + err); + + tty->disc_data = NULL; + sl->tty = NULL; + x25_asy_free(sl); +} + + /************************************************************************ + * STANDARD X.25 ENCAPSULATION * + ************************************************************************/ + +static 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->dev->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 = 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; + default: + return tty_mode_ioctl(tty, file, cmd, arg); + } +} + +#ifdef CONFIG_COMPAT +static long x25_asy_compat_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case SIOCGIFNAME: + case SIOCSIFHWADDR: + return x25_asy_ioctl(tty, file, cmd, + (unsigned long)compat_ptr(arg)); + } + + return -ENOIOCTLCMD; +} +#endif + +static int x25_asy_open_dev(struct net_device *dev) +{ + struct x25_asy *sl = netdev_priv(dev); + if (sl->tty == NULL) + return -ENODEV; + return 0; +} + +static const struct net_device_ops x25_asy_netdev_ops = { + .ndo_open = x25_asy_open_dev, + .ndo_stop = x25_asy_close, + .ndo_start_xmit = x25_asy_xmit, + .ndo_tx_timeout = x25_asy_timeout, + .ndo_change_mtu = x25_asy_change_mtu, +}; + +/* Initialise the X.25 driver. Called by the device init code */ +static void x25_asy_setup(struct net_device *dev) +{ + struct x25_asy *sl = netdev_priv(dev); + + 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->min_mtu = 0; + dev->max_mtu = 65534; + dev->netdev_ops = &x25_asy_netdev_ops; + dev->watchdog_timeo = HZ*20; + 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_ops 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, +#ifdef CONFIG_COMPAT + .compat_ioctl = x25_asy_compat_ioctl, +#endif + .receive_buf = x25_asy_receive_buf, + .write_wakeup = x25_asy_write_wakeup, +}; + +static int __init init_x25_asy(void) +{ + if (x25_asy_maxdev < 4) + x25_asy_maxdev = 4; /* Sanity */ + + pr_info("X.25 async: version 0.00 ALPHA (dynamic channels, max=%d)\n", + x25_asy_maxdev); + + x25_asy_devs = kcalloc(x25_asy_maxdev, sizeof(struct net_device *), + GFP_KERNEL); + if (!x25_asy_devs) + return -ENOMEM; + + 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 = netdev_priv(dev); + + 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_unregister_ldisc(N_X25); +} + +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 000000000..eb4a4216e --- /dev/null +++ b/drivers/net/wan/x25_asy.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 */ + 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 + +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 000000000..deea41e96 --- /dev/null +++ b/drivers/net/wan/z85230.c @@ -0,0 +1,1793 @@ +/* + * 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@lxorguk.ukuu.org.uk> + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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/hdlc.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/gfp.h> +#include <asm/dma.h> +#include <asm/io.h> +#define RT_LOCK +#define RT_UNLOCK +#include <linux/spinlock.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; + + 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) + { + pr_warn("%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); +} + + +/** + * 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) +{ + while(c->txcount) { + /* FIFO full ? */ + if(!(read_zsreg(c, R0)&4)) + return; + 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); +} + +/** + * 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. + */ + +static void z8530_status(struct z8530_channel *chan) +{ + u8 status, altered; + + status = read_zsreg(chan, R0); + altered = chan->status ^ status; + + chan->status = status; + + if (status & TxEOM) { +/* printk("%s: Tx underrun.\n", chan->dev->name); */ + chan->netdevice->stats.tx_fifo_errors++; + write_zsctrl(chan, ERR_RES); + z8530_tx_done(chan); + } + + if (altered & chan->dcdcheck) + { + if (status & chan->dcdcheck) { + pr_info("%s: DCD raised\n", chan->dev->name); + write_zsreg(chan, R3, chan->regs[3] | RxENABLE); + if (chan->netdevice) + netif_carrier_on(chan->netdevice); + } else { + pr_info("%s: DCD lost\n", chan->dev->name); + write_zsreg(chan, R3, chan->regs[3] & ~RxENABLE); + z8530_flush_fifo(chan); + if (chan->netdevice) + netif_carrier_off(chan->netdevice); + } + + } + write_zsctrl(chan, RES_EXT_INT); + write_zsctrl(chan, RES_H_IUS); +} + +struct z8530_irqhandler z8530_sync = { + .rx = z8530_rx, + .tx = z8530_tx, + .status = 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) +{ + 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); + } +} + +/** + * 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) +{ + if(!chan->dma_tx) + { + pr_warn("Hey who turned the DMA off?\n"); + z8530_tx(chan); + return; + } + /* This shouldn't occur in DMA mode */ + pr_err("DMA tx - bogus event!\n"); + z8530_tx(chan); +} + +/** + * 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. + * + */ + +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); + } + } + + if (altered & chan->dcdcheck) + { + if (status & chan->dcdcheck) { + pr_info("%s: DCD raised\n", chan->dev->name); + write_zsreg(chan, R3, chan->regs[3] | RxENABLE); + if (chan->netdevice) + netif_carrier_on(chan->netdevice); + } else { + pr_info("%s: DCD lost\n", chan->dev->name); + write_zsreg(chan, R3, chan->regs[3] & ~RxENABLE); + z8530_flush_fifo(chan); + if (chan->netdevice) + netif_carrier_off(chan->netdevice); + } + } + + write_zsctrl(chan, RES_EXT_INT); + write_zsctrl(chan, RES_H_IUS); +} + +static struct z8530_irqhandler z8530_dma_sync = { + .rx = z8530_dma_rx, + .tx = z8530_dma_tx, + .status = z8530_dma_status, +}; + +static struct z8530_irqhandler z8530_txdma_sync = { + .rx = z8530_rx, + .tx = z8530_dma_tx, + .status = z8530_dma_status, +}; + +/** + * 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 = { + .rx = z8530_rx_clear, + .tx = z8530_tx_clear, + .status = 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. + * + * 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 z8530_dev *dev=dev_id; + u8 uninitialized_var(intr); + static volatile int locker=0; + int work=0; + struct z8530_irqhandler *irqs; + + if(locker) + { + pr_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) + pr_err("%s: interrupt jammed - abort(0x%X)!\n", + dev->name, intr); + /* Ok all done */ + locker=0; + return IRQ_HANDLED; +} + +EXPORT_SYMBOL(z8530_interrupt); + +static const u8 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 transmission. 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 const 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 isn't 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) +{ + pr_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->netdevice->stats.tx_dropped++; + c->netdevice->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->netdevice->stats.tx_packets++; + c->netdevice->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 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. Necessary 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 */ + netdev_warn(c->netdevice, "DMA flip overrun!\n"); + + 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->netdevice->stats.rx_dropped++; + netdev_warn(c->netdevice, "Memory squeeze\n"); + } else { + skb_put(skb, ct); + skb_copy_to_linear_data(skb, rxb, ct); + c->netdevice->stats.rx_packets++; + c->netdevice->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 isn't 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) + netdev_warn(c->netdevice, "memory squeeze\n"); + else + skb_put(c->skb2, c->mtu); + c->netdevice->stats.rx_packets++; + c->netdevice->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->netdevice->stats.rx_dropped++; + netdev_err(c->netdevice, "Lost a frame\n"); + } +} + +/** + * 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. + */ + +netdev_tx_t 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 NETDEV_TX_BUSY; + + + /* 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 */ + skb_copy_from_linear_data(skb, c->tx_next_ptr, 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 NETDEV_TX_OK; +} + +EXPORT_SYMBOL(z8530_queue_xmit); + +/* + * Module support + */ +static const char banner[] __initconst = + 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 000000000..32ae710d4 --- /dev/null +++ b/drivers/net/wan/z85230.h @@ -0,0 +1,448 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Description of Z8530 Z85C30 and Z85230 communications chips + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Alan Cox <alan@lxorguk.ukuu.org.uk> + */ + +#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 */ + + /* + * 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; /* Device 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[]; +irqreturn_t z8530_interrupt(int, void *); +void z8530_describe(struct z8530_dev *, char *mapping, unsigned long io); +int z8530_init(struct z8530_dev *); +int z8530_shutdown(struct z8530_dev *); +int z8530_sync_open(struct net_device *, struct z8530_channel *); +int z8530_sync_close(struct net_device *, struct z8530_channel *); +int z8530_sync_dma_open(struct net_device *, struct z8530_channel *); +int z8530_sync_dma_close(struct net_device *, struct z8530_channel *); +int z8530_sync_txdma_open(struct net_device *, struct z8530_channel *); +int z8530_sync_txdma_close(struct net_device *, struct z8530_channel *); +int z8530_channel_load(struct z8530_channel *, u8 *); +netdev_tx_t z8530_queue_xmit(struct z8530_channel *c, struct sk_buff *skb); +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) */ |