From 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 12:05:51 +0200 Subject: Adding upstream version 5.10.209. Signed-off-by: Daniel Baumann --- drivers/atm/.gitignore | 5 + drivers/atm/Kconfig | 404 ++++ drivers/atm/Makefile | 36 + drivers/atm/adummy.c | 203 ++ drivers/atm/ambassador.c | 2400 ++++++++++++++++++++++++ drivers/atm/ambassador.h | 648 +++++++ drivers/atm/atmtcp.c | 499 +++++ drivers/atm/eni.c | 2321 +++++++++++++++++++++++ drivers/atm/eni.h | 136 ++ drivers/atm/firestream.c | 2056 ++++++++++++++++++++ drivers/atm/firestream.h | 502 +++++ drivers/atm/fore200e.c | 3020 ++++++++++++++++++++++++++++++ drivers/atm/fore200e.h | 974 ++++++++++ drivers/atm/he.c | 2866 ++++++++++++++++++++++++++++ drivers/atm/he.h | 845 +++++++++ drivers/atm/horizon.c | 2853 ++++++++++++++++++++++++++++ drivers/atm/horizon.h | 492 +++++ drivers/atm/idt77105.c | 375 ++++ drivers/atm/idt77105.h | 92 + drivers/atm/idt77252.c | 3806 ++++++++++++++++++++++++++++++++++++++ drivers/atm/idt77252.h | 816 ++++++++ drivers/atm/idt77252_tables.h | 781 ++++++++ drivers/atm/iphase.c | 3288 ++++++++++++++++++++++++++++++++ drivers/atm/iphase.h | 1453 +++++++++++++++ drivers/atm/lanai.c | 2604 ++++++++++++++++++++++++++ drivers/atm/midway.h | 266 +++ drivers/atm/nicstar.c | 2752 +++++++++++++++++++++++++++ drivers/atm/nicstar.h | 759 ++++++++ drivers/atm/nicstarmac.c | 249 +++ drivers/atm/nicstarmac.copyright | 61 + drivers/atm/solos-attrlist.c | 83 + drivers/atm/solos-pci.c | 1496 +++++++++++++++ drivers/atm/suni.c | 391 ++++ drivers/atm/suni.h | 242 +++ drivers/atm/tonga.h | 21 + drivers/atm/uPD98401.h | 293 +++ drivers/atm/uPD98402.c | 266 +++ drivers/atm/uPD98402.h | 107 ++ drivers/atm/zatm.c | 1652 +++++++++++++++++ drivers/atm/zatm.h | 104 ++ drivers/atm/zeprom.h | 35 + 41 files changed, 42252 insertions(+) create mode 100644 drivers/atm/.gitignore create mode 100644 drivers/atm/Kconfig create mode 100644 drivers/atm/Makefile create mode 100644 drivers/atm/adummy.c create mode 100644 drivers/atm/ambassador.c create mode 100644 drivers/atm/ambassador.h create mode 100644 drivers/atm/atmtcp.c create mode 100644 drivers/atm/eni.c create mode 100644 drivers/atm/eni.h create mode 100644 drivers/atm/firestream.c create mode 100644 drivers/atm/firestream.h create mode 100644 drivers/atm/fore200e.c create mode 100644 drivers/atm/fore200e.h create mode 100644 drivers/atm/he.c create mode 100644 drivers/atm/he.h create mode 100644 drivers/atm/horizon.c create mode 100644 drivers/atm/horizon.h create mode 100644 drivers/atm/idt77105.c create mode 100644 drivers/atm/idt77105.h create mode 100644 drivers/atm/idt77252.c create mode 100644 drivers/atm/idt77252.h create mode 100644 drivers/atm/idt77252_tables.h create mode 100644 drivers/atm/iphase.c create mode 100644 drivers/atm/iphase.h create mode 100644 drivers/atm/lanai.c create mode 100644 drivers/atm/midway.h create mode 100644 drivers/atm/nicstar.c create mode 100644 drivers/atm/nicstar.h create mode 100644 drivers/atm/nicstarmac.c create mode 100644 drivers/atm/nicstarmac.copyright create mode 100644 drivers/atm/solos-attrlist.c create mode 100644 drivers/atm/solos-pci.c create mode 100644 drivers/atm/suni.c create mode 100644 drivers/atm/suni.h create mode 100644 drivers/atm/tonga.h create mode 100644 drivers/atm/uPD98401.h create mode 100644 drivers/atm/uPD98402.c create mode 100644 drivers/atm/uPD98402.h create mode 100644 drivers/atm/zatm.c create mode 100644 drivers/atm/zatm.h create mode 100644 drivers/atm/zeprom.h (limited to 'drivers/atm') diff --git a/drivers/atm/.gitignore b/drivers/atm/.gitignore new file mode 100644 index 000000000..ddd374e91 --- /dev/null +++ b/drivers/atm/.gitignore @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +fore200e_mkfirm +fore200e_pca_fw.c +pca200e.bin +pca200e_ecd.bin2 diff --git a/drivers/atm/Kconfig b/drivers/atm/Kconfig new file mode 100644 index 000000000..b9370bbca --- /dev/null +++ b/drivers/atm/Kconfig @@ -0,0 +1,404 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# ATM device configuration +# + +menuconfig ATM_DRIVERS + bool "ATM drivers" + depends on NETDEVICES && ATM + default y + help + Say Y here to get to see options for Asynchronous Transfer Mode + device drivers. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if ATM_DRIVERS && NETDEVICES && ATM + +config ATM_DUMMY + tristate "Dummy ATM driver" + help + Dummy ATM driver. Useful for proxy signalling, testing, + and development. If unsure, say N. + +config ATM_TCP + tristate "ATM over TCP" + depends on INET + help + ATM over TCP driver. Useful mainly for development and for + experiments. If unsure, say N. + +config ATM_LANAI + tristate "Efficient Networks Speedstream 3010" + depends on PCI && ATM + help + Supports ATM cards based on the Efficient Networks "Lanai" + chipset such as the Speedstream 3010 and the ENI-25p. The + Speedstream 3060 is currently not supported since we don't + have the code to drive the on-board Alcatel DSL chipset (yet). + +config ATM_ENI + tristate "Efficient Networks ENI155P" + depends on PCI + help + Driver for the Efficient Networks ENI155p series and SMC ATM + Power155 155 Mbps ATM adapters. Both, the versions with 512KB and + 2MB on-board RAM (Efficient calls them "C" and "S", respectively), + and the FPGA and the ASIC Tonga versions of the board are supported. + The driver works with MMF (-MF or ...F) and UTP-5 (-U5 or ...D) + adapters. + + To compile this driver as a module, choose M here: the module will + be called eni. + +config ATM_ENI_DEBUG + bool "Enable extended debugging" + depends on ATM_ENI + help + Extended debugging records various events and displays that list + when an inconsistency is detected. This mechanism is faster than + generally using printks, but still has some impact on performance. + Note that extended debugging may create certain race conditions + itself. Enable this ONLY if you suspect problems with the driver. + +config ATM_ENI_TUNE_BURST + bool "Fine-tune burst settings" + depends on ATM_ENI + help + In order to obtain good throughput, the ENI NIC can transfer + multiple words of data per PCI bus access cycle. Such a multi-word + transfer is called a burst. + + The default settings for the burst sizes are suitable for most PCI + chipsets. However, in some cases, large bursts may overrun buffers + in the PCI chipset and cause data corruption. In such cases, large + bursts must be disabled and only (slower) small bursts can be used. + The burst sizes can be set independently in the send (TX) and + receive (RX) direction. + + Note that enabling many different burst sizes in the same direction + may increase the cost of setting up a transfer such that the + resulting throughput is lower than when using only the largest + available burst size. + + Also, sometimes larger bursts lead to lower throughput, e.g. on an + Intel 440FX board, a drop from 135 Mbps to 103 Mbps was observed + when going from 8W to 16W bursts. + +config ATM_ENI_BURST_TX_16W + bool "Enable 16W TX bursts (discouraged)" + depends on ATM_ENI_TUNE_BURST + help + Burst sixteen words at once in the send direction. This may work + with recent PCI chipsets, but is known to fail with older chipsets. + +config ATM_ENI_BURST_TX_8W + bool "Enable 8W TX bursts (recommended)" + depends on ATM_ENI_TUNE_BURST + help + Burst eight words at once in the send direction. This is the default + setting. + +config ATM_ENI_BURST_TX_4W + bool "Enable 4W TX bursts (optional)" + depends on ATM_ENI_TUNE_BURST + help + Burst four words at once in the send direction. You may want to try + this if you have disabled 8W bursts. Enabling 4W if 8W is also set + may or may not improve throughput. + +config ATM_ENI_BURST_TX_2W + bool "Enable 2W TX bursts (optional)" + depends on ATM_ENI_TUNE_BURST + help + Burst two words at once in the send direction. You may want to try + this if you have disabled 4W and 8W bursts. Enabling 2W if 4W or 8W + are also set may or may not improve throughput. + +config ATM_ENI_BURST_RX_16W + bool "Enable 16W RX bursts (discouraged)" + depends on ATM_ENI_TUNE_BURST + help + Burst sixteen words at once in the receive direction. This may work + with recent PCI chipsets, but is known to fail with older chipsets. + +config ATM_ENI_BURST_RX_8W + bool "Enable 8W RX bursts (discouraged)" + depends on ATM_ENI_TUNE_BURST + help + Burst eight words at once in the receive direction. This may work + with recent PCI chipsets, but is known to fail with older chipsets, + such as the Intel Neptune series. + +config ATM_ENI_BURST_RX_4W + bool "Enable 4W RX bursts (recommended)" + depends on ATM_ENI_TUNE_BURST + help + Burst four words at once in the receive direction. This is the + default setting. Enabling 4W if 8W is also set may or may not + improve throughput. + +config ATM_ENI_BURST_RX_2W + bool "Enable 2W RX bursts (optional)" + depends on ATM_ENI_TUNE_BURST + help + Burst two words at once in the receive direction. You may want to + try this if you have disabled 4W and 8W bursts. Enabling 2W if 4W or + 8W are also set may or may not improve throughput. + +config ATM_FIRESTREAM + tristate "Fujitsu FireStream (FS50/FS155) " + depends on PCI && VIRT_TO_BUS + help + Driver for the Fujitsu FireStream 155 (MB86697) and + FireStream 50 (MB86695) ATM PCI chips. + + To compile this driver as a module, choose M here: the module will + be called firestream. + +config ATM_ZATM + tristate "ZeitNet ZN1221/ZN1225" + depends on PCI && VIRT_TO_BUS + help + Driver for the ZeitNet ZN1221 (MMF) and ZN1225 (UTP-5) 155 Mbps ATM + adapters. + + To compile this driver as a module, choose M here: the module will + be called zatm. + +config ATM_ZATM_DEBUG + bool "Enable extended debugging" + depends on ATM_ZATM + help + Extended debugging records various events and displays that list + when an inconsistency is detected. This mechanism is faster than + generally using printks, but still has some impact on performance. + Note that extended debugging may create certain race conditions + itself. Enable this ONLY if you suspect problems with the driver. + +config ATM_NICSTAR + tristate "IDT 77201 (NICStAR) (ForeRunnerLE)" + depends on PCI + help + The NICStAR chipset family is used in a large number of ATM NICs for + 25 and for 155 Mbps, including IDT cards and the Fore ForeRunnerLE + series. Say Y if you have one of those. + + To compile this driver as a module, choose M here: the module will + be called nicstar. + +config ATM_NICSTAR_USE_SUNI + bool "Use suni PHY driver (155Mbps)" + depends on ATM_NICSTAR + help + Support for the S-UNI and compatible PHYsical layer chips. These are + found in most 155Mbps NICStAR based ATM cards, namely in the + ForeRunner LE155 cards. This driver provides detection of cable~ + removal and reinsertion and provides some statistics. This driver + doesn't have removal capability when compiled as a module, so if you + need that capability don't include S-UNI support (it's not needed to + make the card work). + +config ATM_NICSTAR_USE_IDT77105 + bool "Use IDT77105 PHY driver (25Mbps)" + depends on ATM_NICSTAR + help + Support for the PHYsical layer chip in ForeRunner LE25 cards. In + addition to cable removal/reinsertion detection, this driver allows + you to control the loopback mode of the chip via a dedicated IOCTL. + This driver is required for proper handling of temporary carrier + loss, so if you have a 25Mbps NICStAR based ATM card you must say Y. + +config ATM_IDT77252 + tristate "IDT 77252 (NICStAR II)" + depends on PCI + help + Driver for the IDT 77252 ATM PCI chips. + + To compile this driver as a module, choose M here: the module will + be called idt77252. + +config ATM_IDT77252_DEBUG + bool "Enable debugging messages" + depends on ATM_IDT77252 + help + Somewhat useful debugging messages are available. The choice of + messages is controlled by a bitmap. This may be specified as a + module argument. See the file for + the meanings of the bits in the mask. + + When active, these messages can have a significant impact on the + speed of the driver, and the size of your syslog files! When + inactive, they will have only a modest impact on performance. + +config ATM_IDT77252_RCV_ALL + bool "Receive ALL cells in raw queue" + depends on ATM_IDT77252 + help + Enable receiving of all cells on the ATM link, that do not match + an open connection in the raw cell queue of the driver. Useful + for debugging or special applications only, so the safe answer is N. + +config ATM_IDT77252_USE_SUNI + bool + depends on ATM_IDT77252 + default y + +config ATM_AMBASSADOR + tristate "Madge Ambassador (Collage PCI 155 Server)" + depends on PCI && VIRT_TO_BUS + select BITREVERSE + help + This is a driver for ATMizer based ATM card produced by Madge + Networks Ltd. Say Y (or M to compile as a module named ambassador) + here if you have one of these cards. + +config ATM_AMBASSADOR_DEBUG + bool "Enable debugging messages" + depends on ATM_AMBASSADOR + help + Somewhat useful debugging messages are available. The choice of + messages is controlled by a bitmap. This may be specified as a + module argument (kernel command line argument as well?), changed + dynamically using an ioctl (not yet) or changed by sending the + string "Dxxxx" to VCI 1023 (where x is a hex digit). See the file + for the meanings of the bits in the + mask. + + When active, these messages can have a significant impact on the + speed of the driver, and the size of your syslog files! When + inactive, they will have only a modest impact on performance. + +config ATM_HORIZON + tristate "Madge Horizon [Ultra] (Collage PCI 25 and Collage PCI 155 Client)" + depends on PCI && VIRT_TO_BUS + help + This is a driver for the Horizon chipset ATM adapter cards once + produced by Madge Networks Ltd. Say Y (or M to compile as a module + named horizon) here if you have one of these cards. + +config ATM_HORIZON_DEBUG + bool "Enable debugging messages" + depends on ATM_HORIZON + help + Somewhat useful debugging messages are available. The choice of + messages is controlled by a bitmap. This may be specified as a + module argument (kernel command line argument as well?), changed + dynamically using an ioctl (not yet) or changed by sending the + string "Dxxxx" to VCI 1023 (where x is a hex digit). See the file + for the meanings of the bits in the + mask. + + When active, these messages can have a significant impact on the + speed of the driver, and the size of your syslog files! When + inactive, they will have only a modest impact on performance. + +config ATM_IA + tristate "Interphase ATM PCI x575/x525/x531" + depends on PCI + help + This is a driver for the Interphase (i)ChipSAR adapter cards + which include a variety of variants in term of the size of the + control memory (128K-1KVC, 512K-4KVC), the size of the packet + memory (128K, 512K, 1M), and the PHY type (Single/Multi mode OC3, + UTP155, UTP25, DS3 and E3). Go to: + + for more info about the cards. Say Y (or M to compile as a module + named iphase) here if you have one of these cards. + + See the file + + for further details. + +config ATM_IA_DEBUG + bool "Enable debugging messages" + depends on ATM_IA + help + Somewhat useful debugging messages are available. The choice of + messages is controlled by a bitmap. This may be specified as a + module argument (kernel command line argument as well?), changed + dynamically using an ioctl (Get the debug utility, iadbg, from + ). + + See the file for the meanings of the + bits in the mask. + + When active, these messages can have a significant impact on the + speed of the driver, and the size of your syslog files! When + inactive, they will have only a modest impact on performance. + +config ATM_FORE200E + tristate "FORE Systems 200E-series" + depends on (PCI || SBUS) + select FW_LOADER + help + This is a driver for the FORE Systems 200E-series ATM adapter + cards. It simultaneously supports PCA-200E and SBA-200E models + on PCI and SBUS hosts. Say Y (or M to compile as a module + named fore_200e) here if you have one of these ATM adapters. + + See the file + for + further details. + +config ATM_FORE200E_USE_TASKLET + bool "Defer interrupt work to a tasklet" + depends on ATM_FORE200E + default n + help + This defers work to be done by the interrupt handler to a + tasklet instead of handling everything at interrupt time. This + may improve the responsive of the host. + +config ATM_FORE200E_TX_RETRY + int "Maximum number of tx retries" + depends on ATM_FORE200E + default "16" + help + Specifies the number of times the driver attempts to transmit + a message before giving up, if the transmit queue of the ATM card + is transiently saturated. + + Saturation of the transmit queue may occur only under extreme + conditions, e.g. when a fast host continuously submits very small + frames (<64 bytes) or raw AAL0 cells (48 bytes) to the ATM adapter. + + Note that under common conditions, it is unlikely that you encounter + a saturation of the transmit queue, so the retry mechanism never + comes into play. + +config ATM_FORE200E_DEBUG + int "Debugging level (0-3)" + depends on ATM_FORE200E + default "0" + help + Specifies the level of debugging messages issued by the driver. + The verbosity of the driver increases with the value of this + parameter. + + When active, these messages can have a significant impact on + the performances of the driver, and the size of your syslog files! + Keep the debugging level to 0 during normal operations. + +config ATM_HE + tristate "ForeRunner HE Series" + depends on PCI + help + This is a driver for the Marconi ForeRunner HE-series ATM adapter + cards. It simultaneously supports the 155 and 622 versions. + +config ATM_HE_USE_SUNI + bool "Use S/UNI PHY driver" + depends on ATM_HE + help + Support for the S/UNI-Ultra and S/UNI-622 found in the ForeRunner + HE cards. This driver provides carrier detection some statistics. + +config ATM_SOLOS + tristate "Solos ADSL2+ PCI Multiport card driver" + depends on PCI + select FW_LOADER + help + Support for the Solos multiport ADSL2+ card. + +endif # ATM diff --git a/drivers/atm/Makefile b/drivers/atm/Makefile new file mode 100644 index 000000000..aa191616a --- /dev/null +++ b/drivers/atm/Makefile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Linux network (ATM) device drivers. +# + +fore_200e-y := fore200e.o + +obj-$(CONFIG_ATM_ZATM) += zatm.o uPD98402.o +obj-$(CONFIG_ATM_NICSTAR) += nicstar.o +obj-$(CONFIG_ATM_AMBASSADOR) += ambassador.o +obj-$(CONFIG_ATM_HORIZON) += horizon.o +obj-$(CONFIG_ATM_IA) += iphase.o suni.o +obj-$(CONFIG_ATM_FORE200E) += fore_200e.o +obj-$(CONFIG_ATM_ENI) += eni.o suni.o +obj-$(CONFIG_ATM_IDT77252) += idt77252.o +obj-$(CONFIG_ATM_SOLOS) += solos-pci.o + +ifeq ($(CONFIG_ATM_NICSTAR_USE_SUNI),y) + obj-$(CONFIG_ATM_NICSTAR) += suni.o +endif +ifeq ($(CONFIG_ATM_NICSTAR_USE_IDT77105),y) + obj-$(CONFIG_ATM_NICSTAR) += idt77105.o +endif +ifeq ($(CONFIG_ATM_IDT77252_USE_SUNI),y) + obj-$(CONFIG_ATM_IDT77252) += suni.o +endif + +obj-$(CONFIG_ATM_DUMMY) += adummy.o +obj-$(CONFIG_ATM_TCP) += atmtcp.o +obj-$(CONFIG_ATM_FIRESTREAM) += firestream.o +obj-$(CONFIG_ATM_LANAI) += lanai.o + +obj-$(CONFIG_ATM_HE) += he.o +ifeq ($(CONFIG_ATM_HE_USE_SUNI),y) + obj-$(CONFIG_ATM_HE) += suni.o +endif diff --git a/drivers/atm/adummy.c b/drivers/atm/adummy.c new file mode 100644 index 000000000..8157925af --- /dev/null +++ b/drivers/atm/adummy.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adummy.c: a dummy ATM driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* version definition */ + +#define DRV_VERSION "1.0" + +#define DEV_LABEL "adummy" + +#define ADUMMY_DEV(dev) ((struct adummy_dev *) (dev)->dev_data) + +struct adummy_dev { + struct atm_dev *atm_dev; + + struct list_head entry; +}; + +/* globals */ + +static LIST_HEAD(adummy_devs); + +static ssize_t __set_signal(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct atm_dev *atm_dev = container_of(dev, struct atm_dev, class_dev); + int signal; + + if (sscanf(buf, "%d", &signal) == 1) { + + if (signal < ATM_PHY_SIG_LOST || signal > ATM_PHY_SIG_FOUND) + signal = ATM_PHY_SIG_UNKNOWN; + + atm_dev_signal_change(atm_dev, signal); + return 1; + } + return -EINVAL; +} + +static ssize_t __show_signal(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atm_dev *atm_dev = container_of(dev, struct atm_dev, class_dev); + return sprintf(buf, "%d\n", atm_dev->signal); +} +static DEVICE_ATTR(signal, 0644, __show_signal, __set_signal); + +static struct attribute *adummy_attrs[] = { + &dev_attr_signal.attr, + NULL +}; + +static const struct attribute_group adummy_group_attrs = { + .name = NULL, /* We want them in dev's root folder */ + .attrs = adummy_attrs +}; + +static int __init +adummy_start(struct atm_dev *dev) +{ + dev->ci_range.vpi_bits = 4; + dev->ci_range.vci_bits = 12; + + return 0; +} + +static int +adummy_open(struct atm_vcc *vcc) +{ + short vpi = vcc->vpi; + int vci = vcc->vci; + + if (vci == ATM_VCI_UNSPEC || vpi == ATM_VPI_UNSPEC) + return 0; + + set_bit(ATM_VF_ADDR, &vcc->flags); + set_bit(ATM_VF_READY, &vcc->flags); + + return 0; +} + +static void +adummy_close(struct atm_vcc *vcc) +{ + clear_bit(ATM_VF_READY, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); +} + +static int +adummy_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + atomic_inc(&vcc->stats->tx); + + return 0; +} + +static int +adummy_proc_read(struct atm_dev *dev, loff_t *pos, char *page) +{ + int left = *pos; + + if (!left--) + return sprintf(page, "version %s\n", DRV_VERSION); + + return 0; +} + +static const struct atmdev_ops adummy_ops = +{ + .open = adummy_open, + .close = adummy_close, + .send = adummy_send, + .proc_read = adummy_proc_read, + .owner = THIS_MODULE +}; + +static int __init adummy_init(void) +{ + struct atm_dev *atm_dev; + struct adummy_dev *adummy_dev; + int err = 0; + + printk(KERN_ERR "adummy: version %s\n", DRV_VERSION); + + adummy_dev = kzalloc(sizeof(struct adummy_dev), + GFP_KERNEL); + if (!adummy_dev) { + printk(KERN_ERR DEV_LABEL ": kzalloc() failed\n"); + err = -ENOMEM; + goto out; + } + atm_dev = atm_dev_register(DEV_LABEL, NULL, &adummy_ops, -1, NULL); + if (!atm_dev) { + printk(KERN_ERR DEV_LABEL ": atm_dev_register() failed\n"); + err = -ENODEV; + goto out_kfree; + } + + adummy_dev->atm_dev = atm_dev; + atm_dev->dev_data = adummy_dev; + + if (sysfs_create_group(&atm_dev->class_dev.kobj, &adummy_group_attrs)) + dev_err(&atm_dev->class_dev, "Could not register attrs for adummy\n"); + + if (adummy_start(atm_dev)) { + printk(KERN_ERR DEV_LABEL ": adummy_start() failed\n"); + err = -ENODEV; + goto out_unregister; + } + + list_add(&adummy_dev->entry, &adummy_devs); +out: + return err; + +out_unregister: + atm_dev_deregister(atm_dev); +out_kfree: + kfree(adummy_dev); + goto out; +} + +static void __exit adummy_cleanup(void) +{ + struct adummy_dev *adummy_dev, *next; + + list_for_each_entry_safe(adummy_dev, next, &adummy_devs, entry) { + atm_dev_deregister(adummy_dev->atm_dev); + kfree(adummy_dev); + } +} + +module_init(adummy_init); +module_exit(adummy_cleanup); + +MODULE_AUTHOR("chas williams "); +MODULE_DESCRIPTION("dummy ATM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/atm/ambassador.c b/drivers/atm/ambassador.c new file mode 100644 index 000000000..c039b8a4f --- /dev/null +++ b/drivers/atm/ambassador.c @@ -0,0 +1,2400 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Madge Ambassador ATM Adapter driver. + Copyright (C) 1995-1999 Madge Networks Ltd. + +*/ + +/* * dedicated to the memory of Graham Gordon 1971-1998 * */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ambassador.h" + +#define maintainer_string "Giuliano Procida at Madge Networks " +#define description_string "Madge ATM Ambassador driver" +#define version_string "1.2.4" + +static inline void __init show_version (void) { + printk ("%s version %s\n", description_string, version_string); +} + +/* + + Theory of Operation + + I Hardware, detection, initialisation and shutdown. + + 1. Supported Hardware + + This driver is for the PCI ATMizer-based Ambassador card (except + very early versions). It is not suitable for the similar EISA "TR7" + card. Commercially, both cards are known as Collage Server ATM + adapters. + + The loader supports image transfer to the card, image start and few + other miscellaneous commands. + + Only AAL5 is supported with vpi = 0 and vci in the range 0 to 1023. + + The cards are big-endian. + + 2. Detection + + Standard PCI stuff, the early cards are detected and rejected. + + 3. Initialisation + + The cards are reset and the self-test results are checked. The + microcode image is then transferred and started. This waits for a + pointer to a descriptor containing details of the host-based queues + and buffers and various parameters etc. Once they are processed + normal operations may begin. The BIA is read using a microcode + command. + + 4. Shutdown + + This may be accomplished either by a card reset or via the microcode + shutdown command. Further investigation required. + + 5. Persistent state + + The card reset does not affect PCI configuration (good) or the + contents of several other "shared run-time registers" (bad) which + include doorbell and interrupt control as well as EEPROM and PCI + control. The driver must be careful when modifying these registers + not to touch bits it does not use and to undo any changes at exit. + + II Driver software + + 0. Generalities + + The adapter is quite intelligent (fast) and has a simple interface + (few features). VPI is always zero, 1024 VCIs are supported. There + is limited cell rate support. UBR channels can be capped and ABR + (explicit rate, but not EFCI) is supported. There is no CBR or VBR + support. + + 1. Driver <-> Adapter Communication + + Apart from the basic loader commands, the driver communicates + through three entities: the command queue (CQ), the transmit queue + pair (TXQ) and the receive queue pairs (RXQ). These three entities + are set up by the host and passed to the microcode just after it has + been started. + + All queues are host-based circular queues. They are contiguous and + (due to hardware limitations) have some restrictions as to their + locations in (bus) memory. They are of the "full means the same as + empty so don't do that" variety since the adapter uses pointers + internally. + + The queue pairs work as follows: one queue is for supply to the + adapter, items in it are pending and are owned by the adapter; the + other is the queue for return from the adapter, items in it have + been dealt with by the adapter. The host adds items to the supply + (TX descriptors and free RX buffer descriptors) and removes items + from the return (TX and RX completions). The adapter deals with out + of order completions. + + Interrupts (card to host) and the doorbell (host to card) are used + for signalling. + + 1. CQ + + This is to communicate "open VC", "close VC", "get stats" etc. to + the adapter. At most one command is retired every millisecond by the + card. There is no out of order completion or notification. The + driver needs to check the return code of the command, waiting as + appropriate. + + 2. TXQ + + TX supply items are of variable length (scatter gather support) and + so the queue items are (more or less) pointers to the real thing. + Each TX supply item contains a unique, host-supplied handle (the skb + bus address seems most sensible as this works for Alphas as well, + there is no need to do any endian conversions on the handles). + + TX return items consist of just the handles above. + + 3. RXQ (up to 4 of these with different lengths and buffer sizes) + + RX supply items consist of a unique, host-supplied handle (the skb + bus address again) and a pointer to the buffer data area. + + RX return items consist of the handle above, the VC, length and a + status word. This just screams "oh so easy" doesn't it? + + Note on RX pool sizes: + + Each pool should have enough buffers to handle a back-to-back stream + of minimum sized frames on a single VC. For example: + + frame spacing = 3us (about right) + + delay = IRQ lat + RX handling + RX buffer replenish = 20 (us) (a guess) + + min number of buffers for one VC = 1 + delay/spacing (buffers) + + delay/spacing = latency = (20+2)/3 = 7 (buffers) (rounding up) + + The 20us delay assumes that there is no need to sleep; if we need to + sleep to get buffers we are going to drop frames anyway. + + In fact, each pool should have enough buffers to support the + simultaneous reassembly of a separate frame on each VC and cope with + the case in which frames complete in round robin cell fashion on + each VC. + + Only one frame can complete at each cell arrival, so if "n" VCs are + open, the worst case is to have them all complete frames together + followed by all starting new frames together. + + desired number of buffers = n + delay/spacing + + These are the extreme requirements, however, they are "n+k" for some + "k" so we have only the constant to choose. This is the argument + rx_lats which current defaults to 7. + + Actually, "n ? n+k : 0" is better and this is what is implemented, + subject to the limit given by the pool size. + + 4. Driver locking + + Simple spinlocks are used around the TX and RX queue mechanisms. + Anyone with a faster, working method is welcome to implement it. + + The adapter command queue is protected with a spinlock. We always + wait for commands to complete. + + A more complex form of locking is used around parts of the VC open + and close functions. There are three reasons for a lock: 1. we need + to do atomic rate reservation and release (not used yet), 2. Opening + sometimes involves two adapter commands which must not be separated + by another command on the same VC, 3. the changes to RX pool size + must be atomic. The lock needs to work over context switches, so we + use a semaphore. + + III Hardware Features and Microcode Bugs + + 1. Byte Ordering + + *%^"$&%^$*&^"$(%^$#&^%$(&#%$*(&^#%!"!"!*! + + 2. Memory access + + All structures that are not accessed using DMA must be 4-byte + aligned (not a problem) and must not cross 4MB boundaries. + + There is a DMA memory hole at E0000000-E00000FF (groan). + + TX fragments (DMA read) must not cross 4MB boundaries (would be 16MB + but for a hardware bug). + + RX buffers (DMA write) must not cross 16MB boundaries and must + include spare trailing bytes up to the next 4-byte boundary; they + will be written with rubbish. + + The PLX likes to prefetch; if reading up to 4 u32 past the end of + each TX fragment is not a problem, then TX can be made to go a + little faster by passing a flag at init that disables a prefetch + workaround. We do not pass this flag. (new microcode only) + + Now we: + . Note that alloc_skb rounds up size to a 16byte boundary. + . Ensure all areas do not traverse 4MB boundaries. + . Ensure all areas do not start at a E00000xx bus address. + (I cannot be certain, but this may always hold with Linux) + . Make all failures cause a loud message. + . Discard non-conforming SKBs (causes TX failure or RX fill delay). + . Discard non-conforming TX fragment descriptors (the TX fails). + In the future we could: + . Allow RX areas that traverse 4MB (but not 16MB) boundaries. + . Segment TX areas into some/more fragments, when necessary. + . Relax checks for non-DMA items (ignore hole). + . Give scatter-gather (iovec) requirements using ???. (?) + + 3. VC close is broken (only for new microcode) + + The VC close adapter microcode command fails to do anything if any + frames have been received on the VC but none have been transmitted. + Frames continue to be reassembled and passed (with IRQ) to the + driver. + + IV To Do List + + . Fix bugs! + + . Timer code may be broken. + + . Deal with buggy VC close (somehow) in microcode 12. + + . Handle interrupted and/or non-blocking writes - is this a job for + the protocol layer? + + . Add code to break up TX fragments when they span 4MB boundaries. + + . Add SUNI phy layer (need to know where SUNI lives on card). + + . Implement a tx_alloc fn to (a) satisfy TX alignment etc. and (b) + leave extra headroom space for Ambassador TX descriptors. + + . Understand these elements of struct atm_vcc: recvq (proto?), + sleep, callback, listenq, backlog_quota, reply and user_back. + + . Adjust TX/RX skb allocation to favour IP with LANE/CLIP (configurable). + + . Impose a TX-pending limit (2?) on each VC, help avoid TX q overflow. + + . Decide whether RX buffer recycling is or can be made completely safe; + turn it back on. It looks like Werner is going to axe this. + + . Implement QoS changes on open VCs (involves extracting parts of VC open + and close into separate functions and using them to make changes). + + . Hack on command queue so that someone can issue multiple commands and wait + on the last one (OR only "no-op" or "wait" commands are waited for). + + . Eliminate need for while-schedule around do_command. + +*/ + +static void do_housekeeping (struct timer_list *t); +/********** globals **********/ + +static unsigned short debug = 0; +static unsigned int cmds = 8; +static unsigned int txs = 32; +static unsigned int rxs[NUM_RX_POOLS] = { 64, 64, 64, 64 }; +static unsigned int rxs_bs[NUM_RX_POOLS] = { 4080, 12240, 36720, 65535 }; +static unsigned int rx_lats = 7; +static unsigned char pci_lat = 0; + +static const unsigned long onegigmask = -1 << 30; + +/********** access to adapter **********/ + +static inline void wr_plain (const amb_dev * dev, size_t addr, u32 data) { + PRINTD (DBG_FLOW|DBG_REGS, "wr: %08zx <- %08x", addr, data); +#ifdef AMB_MMIO + dev->membase[addr / sizeof(u32)] = data; +#else + outl (data, dev->iobase + addr); +#endif +} + +static inline u32 rd_plain (const amb_dev * dev, size_t addr) { +#ifdef AMB_MMIO + u32 data = dev->membase[addr / sizeof(u32)]; +#else + u32 data = inl (dev->iobase + addr); +#endif + PRINTD (DBG_FLOW|DBG_REGS, "rd: %08zx -> %08x", addr, data); + return data; +} + +static inline void wr_mem (const amb_dev * dev, size_t addr, u32 data) { + __be32 be = cpu_to_be32 (data); + PRINTD (DBG_FLOW|DBG_REGS, "wr: %08zx <- %08x b[%08x]", addr, data, be); +#ifdef AMB_MMIO + dev->membase[addr / sizeof(u32)] = be; +#else + outl (be, dev->iobase + addr); +#endif +} + +static inline u32 rd_mem (const amb_dev * dev, size_t addr) { +#ifdef AMB_MMIO + __be32 be = dev->membase[addr / sizeof(u32)]; +#else + __be32 be = inl (dev->iobase + addr); +#endif + u32 data = be32_to_cpu (be); + PRINTD (DBG_FLOW|DBG_REGS, "rd: %08zx -> %08x b[%08x]", addr, data, be); + return data; +} + +/********** dump routines **********/ + +static inline void dump_registers (const amb_dev * dev) { +#ifdef DEBUG_AMBASSADOR + if (debug & DBG_REGS) { + size_t i; + PRINTD (DBG_REGS, "reading PLX control: "); + for (i = 0x00; i < 0x30; i += sizeof(u32)) + rd_mem (dev, i); + PRINTD (DBG_REGS, "reading mailboxes: "); + for (i = 0x40; i < 0x60; i += sizeof(u32)) + rd_mem (dev, i); + PRINTD (DBG_REGS, "reading doorb irqev irqen reset:"); + for (i = 0x60; i < 0x70; i += sizeof(u32)) + rd_mem (dev, i); + } +#else + (void) dev; +#endif + return; +} + +static inline void dump_loader_block (volatile loader_block * lb) { +#ifdef DEBUG_AMBASSADOR + unsigned int i; + PRINTDB (DBG_LOAD, "lb @ %p; res: %d, cmd: %d, pay:", + lb, be32_to_cpu (lb->result), be32_to_cpu (lb->command)); + for (i = 0; i < MAX_COMMAND_DATA; ++i) + PRINTDM (DBG_LOAD, " %08x", be32_to_cpu (lb->payload.data[i])); + PRINTDE (DBG_LOAD, ", vld: %08x", be32_to_cpu (lb->valid)); +#else + (void) lb; +#endif + return; +} + +static inline void dump_command (command * cmd) { +#ifdef DEBUG_AMBASSADOR + unsigned int i; + PRINTDB (DBG_CMD, "cmd @ %p, req: %08x, pars:", + cmd, /*be32_to_cpu*/ (cmd->request)); + for (i = 0; i < 3; ++i) + PRINTDM (DBG_CMD, " %08x", /*be32_to_cpu*/ (cmd->args.par[i])); + PRINTDE (DBG_CMD, ""); +#else + (void) cmd; +#endif + return; +} + +static inline void dump_skb (char * prefix, unsigned int vc, struct sk_buff * skb) { +#ifdef DEBUG_AMBASSADOR + unsigned int i; + unsigned char * data = skb->data; + PRINTDB (DBG_DATA, "%s(%u) ", prefix, vc); + for (i=0; ilen && i < 256;i++) + PRINTDM (DBG_DATA, "%02x ", data[i]); + PRINTDE (DBG_DATA,""); +#else + (void) prefix; + (void) vc; + (void) skb; +#endif + return; +} + +/********** check memory areas for use by Ambassador **********/ + +/* see limitations under Hardware Features */ + +static int check_area (void * start, size_t length) { + // assumes length > 0 + const u32 fourmegmask = -1 << 22; + const u32 twofivesixmask = -1 << 8; + const u32 starthole = 0xE0000000; + u32 startaddress = virt_to_bus (start); + u32 lastaddress = startaddress+length-1; + if ((startaddress ^ lastaddress) & fourmegmask || + (startaddress & twofivesixmask) == starthole) { + PRINTK (KERN_ERR, "check_area failure: [%x,%x] - mail maintainer!", + startaddress, lastaddress); + return -1; + } else { + return 0; + } +} + +/********** free an skb (as per ATM device driver documentation) **********/ + +static void amb_kfree_skb (struct sk_buff * skb) { + if (ATM_SKB(skb)->vcc->pop) { + ATM_SKB(skb)->vcc->pop (ATM_SKB(skb)->vcc, skb); + } else { + dev_kfree_skb_any (skb); + } +} + +/********** TX completion **********/ + +static void tx_complete (amb_dev * dev, tx_out * tx) { + tx_simple * tx_descr = bus_to_virt (tx->handle); + struct sk_buff * skb = tx_descr->skb; + + PRINTD (DBG_FLOW|DBG_TX, "tx_complete %p %p", dev, tx); + + // VC layer stats + atomic_inc(&ATM_SKB(skb)->vcc->stats->tx); + + // free the descriptor + kfree (tx_descr); + + // free the skb + amb_kfree_skb (skb); + + dev->stats.tx_ok++; + return; +} + +/********** RX completion **********/ + +static void rx_complete (amb_dev * dev, rx_out * rx) { + struct sk_buff * skb = bus_to_virt (rx->handle); + u16 vc = be16_to_cpu (rx->vc); + // unused: u16 lec_id = be16_to_cpu (rx->lec_id); + u16 status = be16_to_cpu (rx->status); + u16 rx_len = be16_to_cpu (rx->length); + + PRINTD (DBG_FLOW|DBG_RX, "rx_complete %p %p (len=%hu)", dev, rx, rx_len); + + // XXX move this in and add to VC stats ??? + if (!status) { + struct atm_vcc * atm_vcc = dev->rxer[vc]; + dev->stats.rx.ok++; + + if (atm_vcc) { + + if (rx_len <= atm_vcc->qos.rxtp.max_sdu) { + + if (atm_charge (atm_vcc, skb->truesize)) { + + // prepare socket buffer + ATM_SKB(skb)->vcc = atm_vcc; + skb_put (skb, rx_len); + + dump_skb ("<<<", vc, skb); + + // VC layer stats + atomic_inc(&atm_vcc->stats->rx); + __net_timestamp(skb); + // end of our responsibility + atm_vcc->push (atm_vcc, skb); + return; + + } else { + // someone fix this (message), please! + PRINTD (DBG_INFO|DBG_RX, "dropped thanks to atm_charge (vc %hu, truesize %u)", vc, skb->truesize); + // drop stats incremented in atm_charge + } + + } else { + PRINTK (KERN_INFO, "dropped over-size frame"); + // should we count this? + atomic_inc(&atm_vcc->stats->rx_drop); + } + + } else { + PRINTD (DBG_WARN|DBG_RX, "got frame but RX closed for channel %hu", vc); + // this is an adapter bug, only in new version of microcode + } + + } else { + dev->stats.rx.error++; + if (status & CRC_ERR) + dev->stats.rx.badcrc++; + if (status & LEN_ERR) + dev->stats.rx.toolong++; + if (status & ABORT_ERR) + dev->stats.rx.aborted++; + if (status & UNUSED_ERR) + dev->stats.rx.unused++; + } + + dev_kfree_skb_any (skb); + return; +} + +/* + + Note on queue handling. + + Here "give" and "take" refer to queue entries and a queue (pair) + rather than frames to or from the host or adapter. Empty frame + buffers are given to the RX queue pair and returned unused or + containing RX frames. TX frames (well, pointers to TX fragment + lists) are given to the TX queue pair, completions are returned. + +*/ + +/********** command queue **********/ + +// I really don't like this, but it's the best I can do at the moment + +// also, the callers are responsible for byte order as the microcode +// sometimes does 16-bit accesses (yuk yuk yuk) + +static int command_do (amb_dev * dev, command * cmd) { + amb_cq * cq = &dev->cq; + volatile amb_cq_ptrs * ptrs = &cq->ptrs; + command * my_slot; + + PRINTD (DBG_FLOW|DBG_CMD, "command_do %p", dev); + + if (test_bit (dead, &dev->flags)) + return 0; + + spin_lock (&cq->lock); + + // if not full... + if (cq->pending < cq->maximum) { + // remember my slot for later + my_slot = ptrs->in; + PRINTD (DBG_CMD, "command in slot %p", my_slot); + + dump_command (cmd); + + // copy command in + *ptrs->in = *cmd; + cq->pending++; + ptrs->in = NEXTQ (ptrs->in, ptrs->start, ptrs->limit); + + // mail the command + wr_mem (dev, offsetof(amb_mem, mb.adapter.cmd_address), virt_to_bus (ptrs->in)); + + if (cq->pending > cq->high) + cq->high = cq->pending; + spin_unlock (&cq->lock); + + // these comments were in a while-loop before, msleep removes the loop + // go to sleep + // PRINTD (DBG_CMD, "wait: sleeping %lu for command", timeout); + msleep(cq->pending); + + // wait for my slot to be reached (all waiters are here or above, until...) + while (ptrs->out != my_slot) { + PRINTD (DBG_CMD, "wait: command slot (now at %p)", ptrs->out); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + } + + // wait on my slot (... one gets to its slot, and... ) + while (ptrs->out->request != cpu_to_be32 (SRB_COMPLETE)) { + PRINTD (DBG_CMD, "wait: command slot completion"); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + } + + PRINTD (DBG_CMD, "command complete"); + // update queue (... moves the queue along to the next slot) + spin_lock (&cq->lock); + cq->pending--; + // copy command out + *cmd = *ptrs->out; + ptrs->out = NEXTQ (ptrs->out, ptrs->start, ptrs->limit); + spin_unlock (&cq->lock); + + return 0; + } else { + cq->filled++; + spin_unlock (&cq->lock); + return -EAGAIN; + } + +} + +/********** TX queue pair **********/ + +static int tx_give (amb_dev * dev, tx_in * tx) { + amb_txq * txq = &dev->txq; + unsigned long flags; + + PRINTD (DBG_FLOW|DBG_TX, "tx_give %p", dev); + + if (test_bit (dead, &dev->flags)) + return 0; + + spin_lock_irqsave (&txq->lock, flags); + + if (txq->pending < txq->maximum) { + PRINTD (DBG_TX, "TX in slot %p", txq->in.ptr); + + *txq->in.ptr = *tx; + txq->pending++; + txq->in.ptr = NEXTQ (txq->in.ptr, txq->in.start, txq->in.limit); + // hand over the TX and ring the bell + wr_mem (dev, offsetof(amb_mem, mb.adapter.tx_address), virt_to_bus (txq->in.ptr)); + wr_mem (dev, offsetof(amb_mem, doorbell), TX_FRAME); + + if (txq->pending > txq->high) + txq->high = txq->pending; + spin_unlock_irqrestore (&txq->lock, flags); + return 0; + } else { + txq->filled++; + spin_unlock_irqrestore (&txq->lock, flags); + return -EAGAIN; + } +} + +static int tx_take (amb_dev * dev) { + amb_txq * txq = &dev->txq; + unsigned long flags; + + PRINTD (DBG_FLOW|DBG_TX, "tx_take %p", dev); + + spin_lock_irqsave (&txq->lock, flags); + + if (txq->pending && txq->out.ptr->handle) { + // deal with TX completion + tx_complete (dev, txq->out.ptr); + // mark unused again + txq->out.ptr->handle = 0; + // remove item + txq->pending--; + txq->out.ptr = NEXTQ (txq->out.ptr, txq->out.start, txq->out.limit); + + spin_unlock_irqrestore (&txq->lock, flags); + return 0; + } else { + + spin_unlock_irqrestore (&txq->lock, flags); + return -1; + } +} + +/********** RX queue pairs **********/ + +static int rx_give (amb_dev * dev, rx_in * rx, unsigned char pool) { + amb_rxq * rxq = &dev->rxq[pool]; + unsigned long flags; + + PRINTD (DBG_FLOW|DBG_RX, "rx_give %p[%hu]", dev, pool); + + spin_lock_irqsave (&rxq->lock, flags); + + if (rxq->pending < rxq->maximum) { + PRINTD (DBG_RX, "RX in slot %p", rxq->in.ptr); + + *rxq->in.ptr = *rx; + rxq->pending++; + rxq->in.ptr = NEXTQ (rxq->in.ptr, rxq->in.start, rxq->in.limit); + // hand over the RX buffer + wr_mem (dev, offsetof(amb_mem, mb.adapter.rx_address[pool]), virt_to_bus (rxq->in.ptr)); + + spin_unlock_irqrestore (&rxq->lock, flags); + return 0; + } else { + spin_unlock_irqrestore (&rxq->lock, flags); + return -1; + } +} + +static int rx_take (amb_dev * dev, unsigned char pool) { + amb_rxq * rxq = &dev->rxq[pool]; + unsigned long flags; + + PRINTD (DBG_FLOW|DBG_RX, "rx_take %p[%hu]", dev, pool); + + spin_lock_irqsave (&rxq->lock, flags); + + if (rxq->pending && (rxq->out.ptr->status || rxq->out.ptr->length)) { + // deal with RX completion + rx_complete (dev, rxq->out.ptr); + // mark unused again + rxq->out.ptr->status = 0; + rxq->out.ptr->length = 0; + // remove item + rxq->pending--; + rxq->out.ptr = NEXTQ (rxq->out.ptr, rxq->out.start, rxq->out.limit); + + if (rxq->pending < rxq->low) + rxq->low = rxq->pending; + spin_unlock_irqrestore (&rxq->lock, flags); + return 0; + } else { + if (!rxq->pending && rxq->buffers_wanted) + rxq->emptied++; + spin_unlock_irqrestore (&rxq->lock, flags); + return -1; + } +} + +/********** RX Pool handling **********/ + +/* pre: buffers_wanted = 0, post: pending = 0 */ +static void drain_rx_pool (amb_dev * dev, unsigned char pool) { + amb_rxq * rxq = &dev->rxq[pool]; + + PRINTD (DBG_FLOW|DBG_POOL, "drain_rx_pool %p %hu", dev, pool); + + if (test_bit (dead, &dev->flags)) + return; + + /* we are not quite like the fill pool routines as we cannot just + remove one buffer, we have to remove all of them, but we might as + well pretend... */ + if (rxq->pending > rxq->buffers_wanted) { + command cmd; + cmd.request = cpu_to_be32 (SRB_FLUSH_BUFFER_Q); + cmd.args.flush.flags = cpu_to_be32 (pool << SRB_POOL_SHIFT); + while (command_do (dev, &cmd)) + schedule(); + /* the pool may also be emptied via the interrupt handler */ + while (rxq->pending > rxq->buffers_wanted) + if (rx_take (dev, pool)) + schedule(); + } + + return; +} + +static void drain_rx_pools (amb_dev * dev) { + unsigned char pool; + + PRINTD (DBG_FLOW|DBG_POOL, "drain_rx_pools %p", dev); + + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + drain_rx_pool (dev, pool); +} + +static void fill_rx_pool (amb_dev * dev, unsigned char pool, + gfp_t priority) +{ + rx_in rx; + amb_rxq * rxq; + + PRINTD (DBG_FLOW|DBG_POOL, "fill_rx_pool %p %hu %x", dev, pool, priority); + + if (test_bit (dead, &dev->flags)) + return; + + rxq = &dev->rxq[pool]; + while (rxq->pending < rxq->maximum && rxq->pending < rxq->buffers_wanted) { + + struct sk_buff * skb = alloc_skb (rxq->buffer_size, priority); + if (!skb) { + PRINTD (DBG_SKB|DBG_POOL, "failed to allocate skb for RX pool %hu", pool); + return; + } + if (check_area (skb->data, skb->truesize)) { + dev_kfree_skb_any (skb); + return; + } + // cast needed as there is no %? for pointer differences + PRINTD (DBG_SKB, "allocated skb at %p, head %p, area %li", + skb, skb->head, (long) skb_end_offset(skb)); + rx.handle = virt_to_bus (skb); + rx.host_address = cpu_to_be32 (virt_to_bus (skb->data)); + if (rx_give (dev, &rx, pool)) + dev_kfree_skb_any (skb); + + } + + return; +} + +// top up all RX pools +static void fill_rx_pools (amb_dev * dev) { + unsigned char pool; + + PRINTD (DBG_FLOW|DBG_POOL, "fill_rx_pools %p", dev); + + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + fill_rx_pool (dev, pool, GFP_ATOMIC); + + return; +} + +/********** enable host interrupts **********/ + +static void interrupts_on (amb_dev * dev) { + wr_plain (dev, offsetof(amb_mem, interrupt_control), + rd_plain (dev, offsetof(amb_mem, interrupt_control)) + | AMB_INTERRUPT_BITS); +} + +/********** disable host interrupts **********/ + +static void interrupts_off (amb_dev * dev) { + wr_plain (dev, offsetof(amb_mem, interrupt_control), + rd_plain (dev, offsetof(amb_mem, interrupt_control)) + &~ AMB_INTERRUPT_BITS); +} + +/********** interrupt handling **********/ + +static irqreturn_t interrupt_handler(int irq, void *dev_id) { + amb_dev * dev = dev_id; + + PRINTD (DBG_IRQ|DBG_FLOW, "interrupt_handler: %p", dev_id); + + { + u32 interrupt = rd_plain (dev, offsetof(amb_mem, interrupt)); + + // for us or someone else sharing the same interrupt + if (!interrupt) { + PRINTD (DBG_IRQ, "irq not for me: %d", irq); + return IRQ_NONE; + } + + // definitely for us + PRINTD (DBG_IRQ, "FYI: interrupt was %08x", interrupt); + wr_plain (dev, offsetof(amb_mem, interrupt), -1); + } + + { + unsigned int irq_work = 0; + unsigned char pool; + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + while (!rx_take (dev, pool)) + ++irq_work; + while (!tx_take (dev)) + ++irq_work; + + if (irq_work) { + fill_rx_pools (dev); + + PRINTD (DBG_IRQ, "work done: %u", irq_work); + } else { + PRINTD (DBG_IRQ|DBG_WARN, "no work done"); + } + } + + PRINTD (DBG_IRQ|DBG_FLOW, "interrupt_handler done: %p", dev_id); + return IRQ_HANDLED; +} + +/********** make rate (not quite as much fun as Horizon) **********/ + +static int make_rate (unsigned int rate, rounding r, + u16 * bits, unsigned int * actual) { + unsigned char exp = -1; // hush gcc + unsigned int man = -1; // hush gcc + + PRINTD (DBG_FLOW|DBG_QOS, "make_rate %u", rate); + + // rates in cells per second, ITU format (nasty 16-bit floating-point) + // given 5-bit e and 9-bit m: + // rate = EITHER (1+m/2^9)*2^e OR 0 + // bits = EITHER 1<<14 | e<<9 | m OR 0 + // (bit 15 is "reserved", bit 14 "non-zero") + // smallest rate is 0 (special representation) + // largest rate is (1+511/512)*2^31 = 4290772992 (< 2^32-1) + // smallest non-zero rate is (1+0/512)*2^0 = 1 (> 0) + // simple algorithm: + // find position of top bit, this gives e + // remove top bit and shift (rounding if feeling clever) by 9-e + + // ucode bug: please don't set bit 14! so 0 rate not representable + + if (rate > 0xffc00000U) { + // larger than largest representable rate + + if (r == round_up) { + return -EINVAL; + } else { + exp = 31; + man = 511; + } + + } else if (rate) { + // representable rate + + exp = 31; + man = rate; + + // invariant: rate = man*2^(exp-31) + while (!(man & (1<<31))) { + exp = exp - 1; + man = man<<1; + } + + // man has top bit set + // rate = (2^31+(man-2^31))*2^(exp-31) + // rate = (1+(man-2^31)/2^31)*2^exp + man = man<<1; + man &= 0xffffffffU; // a nop on 32-bit systems + // rate = (1+man/2^32)*2^exp + + // exp is in the range 0 to 31, man is in the range 0 to 2^32-1 + // time to lose significance... we want m in the range 0 to 2^9-1 + // rounding presents a minor problem... we first decide which way + // we are rounding (based on given rounding direction and possibly + // the bits of the mantissa that are to be discarded). + + switch (r) { + case round_down: { + // just truncate + man = man>>(32-9); + break; + } + case round_up: { + // check all bits that we are discarding + if (man & (~0U>>9)) { + man = (man>>(32-9)) + 1; + if (man == (1<<9)) { + // no need to check for round up outside of range + man = 0; + exp += 1; + } + } else { + man = (man>>(32-9)); + } + break; + } + case round_nearest: { + // check msb that we are discarding + if (man & (1<<(32-9-1))) { + man = (man>>(32-9)) + 1; + if (man == (1<<9)) { + // no need to check for round up outside of range + man = 0; + exp += 1; + } + } else { + man = (man>>(32-9)); + } + break; + } + } + + } else { + // zero rate - not representable + + if (r == round_down) { + return -EINVAL; + } else { + exp = 0; + man = 0; + } + + } + + PRINTD (DBG_QOS, "rate: man=%u, exp=%hu", man, exp); + + if (bits) + *bits = /* (1<<14) | */ (exp<<9) | man; + + if (actual) + *actual = (exp >= 9) + ? (1 << exp) + (man << (exp-9)) + : (1 << exp) + ((man + (1<<(9-exp-1))) >> (9-exp)); + + return 0; +} + +/********** Linux ATM Operations **********/ + +// some are not yet implemented while others do not make sense for +// this device + +/********** Open a VC **********/ + +static int amb_open (struct atm_vcc * atm_vcc) +{ + int error; + + struct atm_qos * qos; + struct atm_trafprm * txtp; + struct atm_trafprm * rxtp; + u16 tx_rate_bits = -1; // hush gcc + u16 tx_vc_bits = -1; // hush gcc + u16 tx_frame_bits = -1; // hush gcc + + amb_dev * dev = AMB_DEV(atm_vcc->dev); + amb_vcc * vcc; + unsigned char pool = -1; // hush gcc + short vpi = atm_vcc->vpi; + int vci = atm_vcc->vci; + + PRINTD (DBG_FLOW|DBG_VCC, "amb_open %x %x", vpi, vci); + +#ifdef ATM_VPI_UNSPEC + // UNSPEC is deprecated, remove this code eventually + if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) { + PRINTK (KERN_WARNING, "rejecting open with unspecified VPI/VCI (deprecated)"); + return -EINVAL; + } +#endif + + if (!(0 <= vpi && vpi < (1<qos; + + if (qos->aal != ATM_AAL5) { + PRINTD (DBG_QOS, "AAL not supported"); + return -EINVAL; + } + + // traffic parameters + + PRINTD (DBG_QOS, "TX:"); + txtp = &qos->txtp; + if (txtp->traffic_class != ATM_NONE) { + switch (txtp->traffic_class) { + case ATM_UBR: { + // we take "the PCR" as a rate-cap + int pcr = atm_pcr_goal (txtp); + if (!pcr) { + // no rate cap + tx_rate_bits = 0; + tx_vc_bits = TX_UBR; + tx_frame_bits = TX_FRAME_NOTCAP; + } else { + rounding r; + if (pcr < 0) { + r = round_down; + pcr = -pcr; + } else { + r = round_up; + } + error = make_rate (pcr, r, &tx_rate_bits, NULL); + if (error) + return error; + tx_vc_bits = TX_UBR_CAPPED; + tx_frame_bits = TX_FRAME_CAPPED; + } + break; + } +#if 0 + case ATM_ABR: { + pcr = atm_pcr_goal (txtp); + PRINTD (DBG_QOS, "pcr goal = %d", pcr); + break; + } +#endif + default: { + // PRINTD (DBG_QOS, "request for non-UBR/ABR denied"); + PRINTD (DBG_QOS, "request for non-UBR denied"); + return -EINVAL; + } + } + PRINTD (DBG_QOS, "tx_rate_bits=%hx, tx_vc_bits=%hx", + tx_rate_bits, tx_vc_bits); + } + + PRINTD (DBG_QOS, "RX:"); + rxtp = &qos->rxtp; + if (rxtp->traffic_class == ATM_NONE) { + // do nothing + } else { + // choose an RX pool (arranged in increasing size) + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + if ((unsigned int) rxtp->max_sdu <= dev->rxq[pool].buffer_size) { + PRINTD (DBG_VCC|DBG_QOS|DBG_POOL, "chose pool %hu (max_sdu %u <= %u)", + pool, rxtp->max_sdu, dev->rxq[pool].buffer_size); + break; + } + if (pool == NUM_RX_POOLS) { + PRINTD (DBG_WARN|DBG_VCC|DBG_QOS|DBG_POOL, + "no pool suitable for VC (RX max_sdu %d is too large)", + rxtp->max_sdu); + return -EINVAL; + } + + switch (rxtp->traffic_class) { + case ATM_UBR: { + break; + } +#if 0 + case ATM_ABR: { + pcr = atm_pcr_goal (rxtp); + PRINTD (DBG_QOS, "pcr goal = %d", pcr); + break; + } +#endif + default: { + // PRINTD (DBG_QOS, "request for non-UBR/ABR denied"); + PRINTD (DBG_QOS, "request for non-UBR denied"); + return -EINVAL; + } + } + } + + // get space for our vcc stuff + vcc = kmalloc (sizeof(amb_vcc), GFP_KERNEL); + if (!vcc) { + PRINTK (KERN_ERR, "out of memory!"); + return -ENOMEM; + } + atm_vcc->dev_data = (void *) vcc; + + // no failures beyond this point + + // we are not really "immediately before allocating the connection + // identifier in hardware", but it will just have to do! + set_bit(ATM_VF_ADDR,&atm_vcc->flags); + + if (txtp->traffic_class != ATM_NONE) { + command cmd; + + vcc->tx_frame_bits = tx_frame_bits; + + mutex_lock(&dev->vcc_sf); + if (dev->rxer[vci]) { + // RXer on the channel already, just modify rate... + cmd.request = cpu_to_be32 (SRB_MODIFY_VC_RATE); + cmd.args.modify_rate.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.modify_rate.rate = cpu_to_be32 (tx_rate_bits << SRB_RATE_SHIFT); + while (command_do (dev, &cmd)) + schedule(); + // ... and TX flags, preserving the RX pool + cmd.request = cpu_to_be32 (SRB_MODIFY_VC_FLAGS); + cmd.args.modify_flags.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.modify_flags.flags = cpu_to_be32 + ( (AMB_VCC(dev->rxer[vci])->rx_info.pool << SRB_POOL_SHIFT) + | (tx_vc_bits << SRB_FLAGS_SHIFT) ); + while (command_do (dev, &cmd)) + schedule(); + } else { + // no RXer on the channel, just open (with pool zero) + cmd.request = cpu_to_be32 (SRB_OPEN_VC); + cmd.args.open.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.open.flags = cpu_to_be32 (tx_vc_bits << SRB_FLAGS_SHIFT); + cmd.args.open.rate = cpu_to_be32 (tx_rate_bits << SRB_RATE_SHIFT); + while (command_do (dev, &cmd)) + schedule(); + } + dev->txer[vci].tx_present = 1; + mutex_unlock(&dev->vcc_sf); + } + + if (rxtp->traffic_class != ATM_NONE) { + command cmd; + + vcc->rx_info.pool = pool; + + mutex_lock(&dev->vcc_sf); + /* grow RX buffer pool */ + if (!dev->rxq[pool].buffers_wanted) + dev->rxq[pool].buffers_wanted = rx_lats; + dev->rxq[pool].buffers_wanted += 1; + fill_rx_pool (dev, pool, GFP_KERNEL); + + if (dev->txer[vci].tx_present) { + // TXer on the channel already + // switch (from pool zero) to this pool, preserving the TX bits + cmd.request = cpu_to_be32 (SRB_MODIFY_VC_FLAGS); + cmd.args.modify_flags.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.modify_flags.flags = cpu_to_be32 + ( (pool << SRB_POOL_SHIFT) + | (dev->txer[vci].tx_vc_bits << SRB_FLAGS_SHIFT) ); + } else { + // no TXer on the channel, open the VC (with no rate info) + cmd.request = cpu_to_be32 (SRB_OPEN_VC); + cmd.args.open.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.open.flags = cpu_to_be32 (pool << SRB_POOL_SHIFT); + cmd.args.open.rate = cpu_to_be32 (0); + } + while (command_do (dev, &cmd)) + schedule(); + // this link allows RX frames through + dev->rxer[vci] = atm_vcc; + mutex_unlock(&dev->vcc_sf); + } + + // indicate readiness + set_bit(ATM_VF_READY,&atm_vcc->flags); + + return 0; +} + +/********** Close a VC **********/ + +static void amb_close (struct atm_vcc * atm_vcc) { + amb_dev * dev = AMB_DEV (atm_vcc->dev); + amb_vcc * vcc = AMB_VCC (atm_vcc); + u16 vci = atm_vcc->vci; + + PRINTD (DBG_VCC|DBG_FLOW, "amb_close"); + + // indicate unreadiness + clear_bit(ATM_VF_READY,&atm_vcc->flags); + + // disable TXing + if (atm_vcc->qos.txtp.traffic_class != ATM_NONE) { + command cmd; + + mutex_lock(&dev->vcc_sf); + if (dev->rxer[vci]) { + // RXer still on the channel, just modify rate... XXX not really needed + cmd.request = cpu_to_be32 (SRB_MODIFY_VC_RATE); + cmd.args.modify_rate.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.modify_rate.rate = cpu_to_be32 (0); + // ... and clear TX rate flags (XXX to stop RM cell output?), preserving RX pool + } else { + // no RXer on the channel, close channel + cmd.request = cpu_to_be32 (SRB_CLOSE_VC); + cmd.args.close.vc = cpu_to_be32 (vci); // vpi 0 + } + dev->txer[vci].tx_present = 0; + while (command_do (dev, &cmd)) + schedule(); + mutex_unlock(&dev->vcc_sf); + } + + // disable RXing + if (atm_vcc->qos.rxtp.traffic_class != ATM_NONE) { + command cmd; + + // this is (the?) one reason why we need the amb_vcc struct + unsigned char pool = vcc->rx_info.pool; + + mutex_lock(&dev->vcc_sf); + if (dev->txer[vci].tx_present) { + // TXer still on the channel, just go to pool zero XXX not really needed + cmd.request = cpu_to_be32 (SRB_MODIFY_VC_FLAGS); + cmd.args.modify_flags.vc = cpu_to_be32 (vci); // vpi 0 + cmd.args.modify_flags.flags = cpu_to_be32 + (dev->txer[vci].tx_vc_bits << SRB_FLAGS_SHIFT); + } else { + // no TXer on the channel, close the VC + cmd.request = cpu_to_be32 (SRB_CLOSE_VC); + cmd.args.close.vc = cpu_to_be32 (vci); // vpi 0 + } + // forget the rxer - no more skbs will be pushed + if (atm_vcc != dev->rxer[vci]) + PRINTK (KERN_ERR, "%s vcc=%p rxer[vci]=%p", + "arghhh! we're going to die!", + vcc, dev->rxer[vci]); + dev->rxer[vci] = NULL; + while (command_do (dev, &cmd)) + schedule(); + + /* shrink RX buffer pool */ + dev->rxq[pool].buffers_wanted -= 1; + if (dev->rxq[pool].buffers_wanted == rx_lats) { + dev->rxq[pool].buffers_wanted = 0; + drain_rx_pool (dev, pool); + } + mutex_unlock(&dev->vcc_sf); + } + + // free our structure + kfree (vcc); + + // say the VPI/VCI is free again + clear_bit(ATM_VF_ADDR,&atm_vcc->flags); + + return; +} + +/********** Send **********/ + +static int amb_send (struct atm_vcc * atm_vcc, struct sk_buff * skb) { + amb_dev * dev = AMB_DEV(atm_vcc->dev); + amb_vcc * vcc = AMB_VCC(atm_vcc); + u16 vc = atm_vcc->vci; + unsigned int tx_len = skb->len; + unsigned char * tx_data = skb->data; + tx_simple * tx_descr; + tx_in tx; + + if (test_bit (dead, &dev->flags)) + return -EIO; + + PRINTD (DBG_FLOW|DBG_TX, "amb_send vc %x data %p len %u", + vc, tx_data, tx_len); + + dump_skb (">>>", vc, skb); + + if (!dev->txer[vc].tx_present) { + PRINTK (KERN_ERR, "attempt to send on RX-only VC %x", vc); + return -EBADFD; + } + + // this is a driver private field so we have to set it ourselves, + // despite the fact that we are _required_ to use it to check for a + // pop function + ATM_SKB(skb)->vcc = atm_vcc; + + if (skb->len > (size_t) atm_vcc->qos.txtp.max_sdu) { + PRINTK (KERN_ERR, "sk_buff length greater than agreed max_sdu, dropping..."); + return -EIO; + } + + if (check_area (skb->data, skb->len)) { + atomic_inc(&atm_vcc->stats->tx_err); + return -ENOMEM; // ? + } + + // allocate memory for fragments + tx_descr = kmalloc (sizeof(tx_simple), GFP_KERNEL); + if (!tx_descr) { + PRINTK (KERN_ERR, "could not allocate TX descriptor"); + return -ENOMEM; + } + if (check_area (tx_descr, sizeof(tx_simple))) { + kfree (tx_descr); + return -ENOMEM; + } + PRINTD (DBG_TX, "fragment list allocated at %p", tx_descr); + + tx_descr->skb = skb; + + tx_descr->tx_frag.bytes = cpu_to_be32 (tx_len); + tx_descr->tx_frag.address = cpu_to_be32 (virt_to_bus (tx_data)); + + tx_descr->tx_frag_end.handle = virt_to_bus (tx_descr); + tx_descr->tx_frag_end.vc = 0; + tx_descr->tx_frag_end.next_descriptor_length = 0; + tx_descr->tx_frag_end.next_descriptor = 0; +#ifdef AMB_NEW_MICROCODE + tx_descr->tx_frag_end.cpcs_uu = 0; + tx_descr->tx_frag_end.cpi = 0; + tx_descr->tx_frag_end.pad = 0; +#endif + + tx.vc = cpu_to_be16 (vcc->tx_frame_bits | vc); + tx.tx_descr_length = cpu_to_be16 (sizeof(tx_frag)+sizeof(tx_frag_end)); + tx.tx_descr_addr = cpu_to_be32 (virt_to_bus (&tx_descr->tx_frag)); + + while (tx_give (dev, &tx)) + schedule(); + return 0; +} + +/********** Change QoS on a VC **********/ + +// int amb_change_qos (struct atm_vcc * atm_vcc, struct atm_qos * qos, int flags); + +/********** Free RX Socket Buffer **********/ + +#if 0 +static void amb_free_rx_skb (struct atm_vcc * atm_vcc, struct sk_buff * skb) { + amb_dev * dev = AMB_DEV (atm_vcc->dev); + amb_vcc * vcc = AMB_VCC (atm_vcc); + unsigned char pool = vcc->rx_info.pool; + rx_in rx; + + // This may be unsafe for various reasons that I cannot really guess + // at. However, I note that the ATM layer calls kfree_skb rather + // than dev_kfree_skb at this point so we are least covered as far + // as buffer locking goes. There may be bugs if pcap clones RX skbs. + + PRINTD (DBG_FLOW|DBG_SKB, "amb_rx_free skb %p (atm_vcc %p, vcc %p)", + skb, atm_vcc, vcc); + + rx.handle = virt_to_bus (skb); + rx.host_address = cpu_to_be32 (virt_to_bus (skb->data)); + + skb->data = skb->head; + skb_reset_tail_pointer(skb); + skb->len = 0; + + if (!rx_give (dev, &rx, pool)) { + // success + PRINTD (DBG_SKB|DBG_POOL, "recycled skb for pool %hu", pool); + return; + } + + // just do what the ATM layer would have done + dev_kfree_skb_any (skb); + + return; +} +#endif + +/********** Proc File Output **********/ + +static int amb_proc_read (struct atm_dev * atm_dev, loff_t * pos, char * page) { + amb_dev * dev = AMB_DEV (atm_dev); + int left = *pos; + unsigned char pool; + + PRINTD (DBG_FLOW, "amb_proc_read"); + + /* more diagnostics here? */ + + if (!left--) { + amb_stats * s = &dev->stats; + return sprintf (page, + "frames: TX OK %lu, RX OK %lu, RX bad %lu " + "(CRC %lu, long %lu, aborted %lu, unused %lu).\n", + s->tx_ok, s->rx.ok, s->rx.error, + s->rx.badcrc, s->rx.toolong, + s->rx.aborted, s->rx.unused); + } + + if (!left--) { + amb_cq * c = &dev->cq; + return sprintf (page, "cmd queue [cur/hi/max]: %u/%u/%u. ", + c->pending, c->high, c->maximum); + } + + if (!left--) { + amb_txq * t = &dev->txq; + return sprintf (page, "TX queue [cur/max high full]: %u/%u %u %u.\n", + t->pending, t->maximum, t->high, t->filled); + } + + if (!left--) { + unsigned int count = sprintf (page, "RX queues [cur/max/req low empty]:"); + for (pool = 0; pool < NUM_RX_POOLS; ++pool) { + amb_rxq * r = &dev->rxq[pool]; + count += sprintf (page+count, " %u/%u/%u %u %u", + r->pending, r->maximum, r->buffers_wanted, r->low, r->emptied); + } + count += sprintf (page+count, ".\n"); + return count; + } + + if (!left--) { + unsigned int count = sprintf (page, "RX buffer sizes:"); + for (pool = 0; pool < NUM_RX_POOLS; ++pool) { + amb_rxq * r = &dev->rxq[pool]; + count += sprintf (page+count, " %u", r->buffer_size); + } + count += sprintf (page+count, ".\n"); + return count; + } + +#if 0 + if (!left--) { + // suni block etc? + } +#endif + + return 0; +} + +/********** Operation Structure **********/ + +static const struct atmdev_ops amb_ops = { + .open = amb_open, + .close = amb_close, + .send = amb_send, + .proc_read = amb_proc_read, + .owner = THIS_MODULE, +}; + +/********** housekeeping **********/ +static void do_housekeeping (struct timer_list *t) { + amb_dev * dev = from_timer(dev, t, housekeeping); + + // could collect device-specific (not driver/atm-linux) stats here + + // last resort refill once every ten seconds + fill_rx_pools (dev); + mod_timer(&dev->housekeeping, jiffies + 10*HZ); + + return; +} + +/********** creation of communication queues **********/ + +static int create_queues(amb_dev *dev, unsigned int cmds, unsigned int txs, + unsigned int *rxs, unsigned int *rx_buffer_sizes) +{ + unsigned char pool; + size_t total = 0; + void * memory; + void * limit; + + PRINTD (DBG_FLOW, "create_queues %p", dev); + + total += cmds * sizeof(command); + + total += txs * (sizeof(tx_in) + sizeof(tx_out)); + + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + total += rxs[pool] * (sizeof(rx_in) + sizeof(rx_out)); + + memory = kmalloc (total, GFP_KERNEL); + if (!memory) { + PRINTK (KERN_ERR, "could not allocate queues"); + return -ENOMEM; + } + if (check_area (memory, total)) { + PRINTK (KERN_ERR, "queues allocated in nasty area"); + kfree (memory); + return -ENOMEM; + } + + limit = memory + total; + PRINTD (DBG_INIT, "queues from %p to %p", memory, limit); + + PRINTD (DBG_CMD, "command queue at %p", memory); + + { + command * cmd = memory; + amb_cq * cq = &dev->cq; + + cq->pending = 0; + cq->high = 0; + cq->maximum = cmds - 1; + + cq->ptrs.start = cmd; + cq->ptrs.in = cmd; + cq->ptrs.out = cmd; + cq->ptrs.limit = cmd + cmds; + + memory = cq->ptrs.limit; + } + + PRINTD (DBG_TX, "TX queue pair at %p", memory); + + { + tx_in * in = memory; + tx_out * out; + amb_txq * txq = &dev->txq; + + txq->pending = 0; + txq->high = 0; + txq->filled = 0; + txq->maximum = txs - 1; + + txq->in.start = in; + txq->in.ptr = in; + txq->in.limit = in + txs; + + memory = txq->in.limit; + out = memory; + + txq->out.start = out; + txq->out.ptr = out; + txq->out.limit = out + txs; + + memory = txq->out.limit; + } + + PRINTD (DBG_RX, "RX queue pairs at %p", memory); + + for (pool = 0; pool < NUM_RX_POOLS; ++pool) { + rx_in * in = memory; + rx_out * out; + amb_rxq * rxq = &dev->rxq[pool]; + + rxq->buffer_size = rx_buffer_sizes[pool]; + rxq->buffers_wanted = 0; + + rxq->pending = 0; + rxq->low = rxs[pool] - 1; + rxq->emptied = 0; + rxq->maximum = rxs[pool] - 1; + + rxq->in.start = in; + rxq->in.ptr = in; + rxq->in.limit = in + rxs[pool]; + + memory = rxq->in.limit; + out = memory; + + rxq->out.start = out; + rxq->out.ptr = out; + rxq->out.limit = out + rxs[pool]; + + memory = rxq->out.limit; + } + + if (memory == limit) { + return 0; + } else { + PRINTK (KERN_ERR, "bad queue alloc %p != %p (tell maintainer)", memory, limit); + kfree (limit - total); + return -ENOMEM; + } + +} + +/********** destruction of communication queues **********/ + +static void destroy_queues (amb_dev * dev) { + // all queues assumed empty + void * memory = dev->cq.ptrs.start; + // includes txq.in, txq.out, rxq[].in and rxq[].out + + PRINTD (DBG_FLOW, "destroy_queues %p", dev); + + PRINTD (DBG_INIT, "freeing queues at %p", memory); + kfree (memory); + + return; +} + +/********** basic loader commands and error handling **********/ +// centisecond timeouts - guessing away here +static unsigned int command_timeouts [] = { + [host_memory_test] = 15, + [read_adapter_memory] = 2, + [write_adapter_memory] = 2, + [adapter_start] = 50, + [get_version_number] = 10, + [interrupt_host] = 1, + [flash_erase_sector] = 1, + [adap_download_block] = 1, + [adap_erase_flash] = 1, + [adap_run_in_iram] = 1, + [adap_end_download] = 1 +}; + + +static unsigned int command_successes [] = { + [host_memory_test] = COMMAND_PASSED_TEST, + [read_adapter_memory] = COMMAND_READ_DATA_OK, + [write_adapter_memory] = COMMAND_WRITE_DATA_OK, + [adapter_start] = COMMAND_COMPLETE, + [get_version_number] = COMMAND_COMPLETE, + [interrupt_host] = COMMAND_COMPLETE, + [flash_erase_sector] = COMMAND_COMPLETE, + [adap_download_block] = COMMAND_COMPLETE, + [adap_erase_flash] = COMMAND_COMPLETE, + [adap_run_in_iram] = COMMAND_COMPLETE, + [adap_end_download] = COMMAND_COMPLETE +}; + +static int decode_loader_result (loader_command cmd, u32 result) +{ + int res; + const char *msg; + + if (result == command_successes[cmd]) + return 0; + + switch (result) { + case BAD_COMMAND: + res = -EINVAL; + msg = "bad command"; + break; + case COMMAND_IN_PROGRESS: + res = -ETIMEDOUT; + msg = "command in progress"; + break; + case COMMAND_PASSED_TEST: + res = 0; + msg = "command passed test"; + break; + case COMMAND_FAILED_TEST: + res = -EIO; + msg = "command failed test"; + break; + case COMMAND_READ_DATA_OK: + res = 0; + msg = "command read data ok"; + break; + case COMMAND_READ_BAD_ADDRESS: + res = -EINVAL; + msg = "command read bad address"; + break; + case COMMAND_WRITE_DATA_OK: + res = 0; + msg = "command write data ok"; + break; + case COMMAND_WRITE_BAD_ADDRESS: + res = -EINVAL; + msg = "command write bad address"; + break; + case COMMAND_WRITE_FLASH_FAILURE: + res = -EIO; + msg = "command write flash failure"; + break; + case COMMAND_COMPLETE: + res = 0; + msg = "command complete"; + break; + case COMMAND_FLASH_ERASE_FAILURE: + res = -EIO; + msg = "command flash erase failure"; + break; + case COMMAND_WRITE_BAD_DATA: + res = -EINVAL; + msg = "command write bad data"; + break; + default: + res = -EINVAL; + msg = "unknown error"; + PRINTD (DBG_LOAD|DBG_ERR, + "decode_loader_result got %d=%x !", + result, result); + break; + } + + PRINTK (KERN_ERR, "%s", msg); + return res; +} + +static int do_loader_command(volatile loader_block *lb, const amb_dev *dev, + loader_command cmd) +{ + + unsigned long timeout; + + PRINTD (DBG_FLOW|DBG_LOAD, "do_loader_command"); + + /* do a command + + Set the return value to zero, set the command type and set the + valid entry to the right magic value. The payload is already + correctly byte-ordered so we leave it alone. Hit the doorbell + with the bus address of this structure. + + */ + + lb->result = 0; + lb->command = cpu_to_be32 (cmd); + lb->valid = cpu_to_be32 (DMA_VALID); + // dump_registers (dev); + // dump_loader_block (lb); + wr_mem (dev, offsetof(amb_mem, doorbell), virt_to_bus (lb) & ~onegigmask); + + timeout = command_timeouts[cmd] * 10; + + while (!lb->result || lb->result == cpu_to_be32 (COMMAND_IN_PROGRESS)) + if (timeout) { + timeout = msleep_interruptible(timeout); + } else { + PRINTD (DBG_LOAD|DBG_ERR, "command %d timed out", cmd); + dump_registers (dev); + dump_loader_block (lb); + return -ETIMEDOUT; + } + + if (cmd == adapter_start) { + // wait for start command to acknowledge... + timeout = 100; + while (rd_plain (dev, offsetof(amb_mem, doorbell))) + if (timeout) { + timeout = msleep_interruptible(timeout); + } else { + PRINTD (DBG_LOAD|DBG_ERR, "start command did not clear doorbell, res=%08x", + be32_to_cpu (lb->result)); + dump_registers (dev); + return -ETIMEDOUT; + } + return 0; + } else { + return decode_loader_result (cmd, be32_to_cpu (lb->result)); + } + +} + +/* loader: determine loader version */ + +static int get_loader_version(loader_block *lb, const amb_dev *dev, + u32 *version) +{ + int res; + + PRINTD (DBG_FLOW|DBG_LOAD, "get_loader_version"); + + res = do_loader_command (lb, dev, get_version_number); + if (res) + return res; + if (version) + *version = be32_to_cpu (lb->payload.version); + return 0; +} + +/* loader: write memory data blocks */ + +static int loader_write(loader_block *lb, const amb_dev *dev, + const struct ihex_binrec *rec) +{ + transfer_block * tb = &lb->payload.transfer; + + PRINTD (DBG_FLOW|DBG_LOAD, "loader_write"); + + tb->address = rec->addr; + tb->count = cpu_to_be32(be16_to_cpu(rec->len) / 4); + memcpy(tb->data, rec->data, be16_to_cpu(rec->len)); + return do_loader_command (lb, dev, write_adapter_memory); +} + +/* loader: verify memory data blocks */ + +static int loader_verify(loader_block *lb, const amb_dev *dev, + const struct ihex_binrec *rec) +{ + transfer_block * tb = &lb->payload.transfer; + int res; + + PRINTD (DBG_FLOW|DBG_LOAD, "loader_verify"); + + tb->address = rec->addr; + tb->count = cpu_to_be32(be16_to_cpu(rec->len) / 4); + res = do_loader_command (lb, dev, read_adapter_memory); + if (!res && memcmp(tb->data, rec->data, be16_to_cpu(rec->len))) + res = -EINVAL; + return res; +} + +/* loader: start microcode */ + +static int loader_start(loader_block *lb, const amb_dev *dev, u32 address) +{ + PRINTD (DBG_FLOW|DBG_LOAD, "loader_start"); + + lb->payload.start = cpu_to_be32 (address); + return do_loader_command (lb, dev, adapter_start); +} + +/********** reset card **********/ + +static inline void sf (const char * msg) +{ + PRINTK (KERN_ERR, "self-test failed: %s", msg); +} + +static int amb_reset (amb_dev * dev, int diags) { + u32 word; + + PRINTD (DBG_FLOW|DBG_LOAD, "amb_reset"); + + word = rd_plain (dev, offsetof(amb_mem, reset_control)); + // put card into reset state + wr_plain (dev, offsetof(amb_mem, reset_control), word | AMB_RESET_BITS); + // wait a short while + udelay (10); +#if 1 + // put card into known good state + wr_plain (dev, offsetof(amb_mem, interrupt_control), AMB_DOORBELL_BITS); + // clear all interrupts just in case + wr_plain (dev, offsetof(amb_mem, interrupt), -1); +#endif + // clear self-test done flag + wr_plain (dev, offsetof(amb_mem, mb.loader.ready), 0); + // take card out of reset state + wr_plain (dev, offsetof(amb_mem, reset_control), word &~ AMB_RESET_BITS); + + if (diags) { + unsigned long timeout; + // 4.2 second wait + msleep(4200); + // half second time-out + timeout = 500; + while (!rd_plain (dev, offsetof(amb_mem, mb.loader.ready))) + if (timeout) { + timeout = msleep_interruptible(timeout); + } else { + PRINTD (DBG_LOAD|DBG_ERR, "reset timed out"); + return -ETIMEDOUT; + } + + // get results of self-test + // XXX double check byte-order + word = rd_mem (dev, offsetof(amb_mem, mb.loader.result)); + if (word & SELF_TEST_FAILURE) { + if (word & GPINT_TST_FAILURE) + sf ("interrupt"); + if (word & SUNI_DATA_PATTERN_FAILURE) + sf ("SUNI data pattern"); + if (word & SUNI_DATA_BITS_FAILURE) + sf ("SUNI data bits"); + if (word & SUNI_UTOPIA_FAILURE) + sf ("SUNI UTOPIA interface"); + if (word & SUNI_FIFO_FAILURE) + sf ("SUNI cell buffer FIFO"); + if (word & SRAM_FAILURE) + sf ("bad SRAM"); + // better return value? + return -EIO; + } + + } + return 0; +} + +/********** transfer and start the microcode **********/ + +static int ucode_init(loader_block *lb, amb_dev *dev) +{ + const struct firmware *fw; + unsigned long start_address; + const struct ihex_binrec *rec; + const char *errmsg = NULL; + int res; + + res = request_ihex_firmware(&fw, "atmsar11.fw", &dev->pci_dev->dev); + if (res) { + PRINTK (KERN_ERR, "Cannot load microcode data"); + return res; + } + + /* First record contains just the start address */ + rec = (const struct ihex_binrec *)fw->data; + if (be16_to_cpu(rec->len) != sizeof(__be32) || be32_to_cpu(rec->addr)) { + errmsg = "no start record"; + goto fail; + } + start_address = be32_to_cpup((__be32 *)rec->data); + + rec = ihex_next_binrec(rec); + + PRINTD (DBG_FLOW|DBG_LOAD, "ucode_init"); + + while (rec) { + PRINTD (DBG_LOAD, "starting region (%x, %u)", be32_to_cpu(rec->addr), + be16_to_cpu(rec->len)); + if (be16_to_cpu(rec->len) > 4 * MAX_TRANSFER_DATA) { + errmsg = "record too long"; + goto fail; + } + if (be16_to_cpu(rec->len) & 3) { + errmsg = "odd number of bytes"; + goto fail; + } + res = loader_write(lb, dev, rec); + if (res) + break; + + res = loader_verify(lb, dev, rec); + if (res) + break; + rec = ihex_next_binrec(rec); + } + release_firmware(fw); + if (!res) + res = loader_start(lb, dev, start_address); + + return res; +fail: + release_firmware(fw); + PRINTK(KERN_ERR, "Bad microcode data (%s)", errmsg); + return -EINVAL; +} + +/********** give adapter parameters **********/ + +static inline __be32 bus_addr(void * addr) { + return cpu_to_be32 (virt_to_bus (addr)); +} + +static int amb_talk(amb_dev *dev) +{ + adap_talk_block a; + unsigned char pool; + unsigned long timeout; + + PRINTD (DBG_FLOW, "amb_talk %p", dev); + + a.command_start = bus_addr (dev->cq.ptrs.start); + a.command_end = bus_addr (dev->cq.ptrs.limit); + a.tx_start = bus_addr (dev->txq.in.start); + a.tx_end = bus_addr (dev->txq.in.limit); + a.txcom_start = bus_addr (dev->txq.out.start); + a.txcom_end = bus_addr (dev->txq.out.limit); + + for (pool = 0; pool < NUM_RX_POOLS; ++pool) { + // the other "a" items are set up by the adapter + a.rec_struct[pool].buffer_start = bus_addr (dev->rxq[pool].in.start); + a.rec_struct[pool].buffer_end = bus_addr (dev->rxq[pool].in.limit); + a.rec_struct[pool].rx_start = bus_addr (dev->rxq[pool].out.start); + a.rec_struct[pool].rx_end = bus_addr (dev->rxq[pool].out.limit); + a.rec_struct[pool].buffer_size = cpu_to_be32 (dev->rxq[pool].buffer_size); + } + +#ifdef AMB_NEW_MICROCODE + // disable fast PLX prefetching + a.init_flags = 0; +#endif + + // pass the structure + wr_mem (dev, offsetof(amb_mem, doorbell), virt_to_bus (&a)); + + // 2.2 second wait (must not touch doorbell during 2 second DMA test) + msleep(2200); + // give the adapter another half second? + timeout = 500; + while (rd_plain (dev, offsetof(amb_mem, doorbell))) + if (timeout) { + timeout = msleep_interruptible(timeout); + } else { + PRINTD (DBG_INIT|DBG_ERR, "adapter init timed out"); + return -ETIMEDOUT; + } + + return 0; +} + +// get microcode version +static void amb_ucode_version(amb_dev *dev) +{ + u32 major; + u32 minor; + command cmd; + cmd.request = cpu_to_be32 (SRB_GET_VERSION); + while (command_do (dev, &cmd)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + } + major = be32_to_cpu (cmd.args.version.major); + minor = be32_to_cpu (cmd.args.version.minor); + PRINTK (KERN_INFO, "microcode version is %u.%u", major, minor); +} + +// get end station address +static void amb_esi(amb_dev *dev, u8 *esi) +{ + u32 lower4; + u16 upper2; + command cmd; + + cmd.request = cpu_to_be32 (SRB_GET_BIA); + while (command_do (dev, &cmd)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + } + lower4 = be32_to_cpu (cmd.args.bia.lower4); + upper2 = be32_to_cpu (cmd.args.bia.upper2); + PRINTD (DBG_LOAD, "BIA: lower4: %08x, upper2 %04x", lower4, upper2); + + if (esi) { + unsigned int i; + + PRINTDB (DBG_INIT, "ESI:"); + for (i = 0; i < ESI_LEN; ++i) { + if (i < 4) + esi[i] = bitrev8(lower4>>(8*i)); + else + esi[i] = bitrev8(upper2>>(8*(i-4))); + PRINTDM (DBG_INIT, " %02x", esi[i]); + } + + PRINTDE (DBG_INIT, ""); + } + + return; +} + +static void fixup_plx_window (amb_dev *dev, loader_block *lb) +{ + // fix up the PLX-mapped window base address to match the block + unsigned long blb; + u32 mapreg; + blb = virt_to_bus(lb); + // the kernel stack had better not ever cross a 1Gb boundary! + mapreg = rd_plain (dev, offsetof(amb_mem, stuff[10])); + mapreg &= ~onegigmask; + mapreg |= blb & onegigmask; + wr_plain (dev, offsetof(amb_mem, stuff[10]), mapreg); + return; +} + +static int amb_init(amb_dev *dev) +{ + loader_block lb; + + u32 version; + + if (amb_reset (dev, 1)) { + PRINTK (KERN_ERR, "card reset failed!"); + } else { + fixup_plx_window (dev, &lb); + + if (get_loader_version (&lb, dev, &version)) { + PRINTK (KERN_INFO, "failed to get loader version"); + } else { + PRINTK (KERN_INFO, "loader version is %08x", version); + + if (ucode_init (&lb, dev)) { + PRINTK (KERN_ERR, "microcode failure"); + } else if (create_queues (dev, cmds, txs, rxs, rxs_bs)) { + PRINTK (KERN_ERR, "failed to get memory for queues"); + } else { + + if (amb_talk (dev)) { + PRINTK (KERN_ERR, "adapter did not accept queues"); + } else { + + amb_ucode_version (dev); + return 0; + + } /* amb_talk */ + + destroy_queues (dev); + } /* create_queues, ucode_init */ + + amb_reset (dev, 0); + } /* get_loader_version */ + + } /* amb_reset */ + + return -EINVAL; +} + +static void setup_dev(amb_dev *dev, struct pci_dev *pci_dev) +{ + unsigned char pool; + + // set up known dev items straight away + dev->pci_dev = pci_dev; + pci_set_drvdata(pci_dev, dev); + + dev->iobase = pci_resource_start (pci_dev, 1); + dev->irq = pci_dev->irq; + dev->membase = bus_to_virt(pci_resource_start(pci_dev, 0)); + + // flags (currently only dead) + dev->flags = 0; + + // Allocate cell rates (fibre) + // ATM_OC3_PCR = 1555200000/8/270*260/53 - 29/53 + // to be really pedantic, this should be ATM_OC3c_PCR + dev->tx_avail = ATM_OC3_PCR; + dev->rx_avail = ATM_OC3_PCR; + + // semaphore for txer/rxer modifications - we cannot use a + // spinlock as the critical region needs to switch processes + mutex_init(&dev->vcc_sf); + // queue manipulation spinlocks; we want atomic reads and + // writes to the queue descriptors (handles IRQ and SMP) + // consider replacing "int pending" -> "atomic_t available" + // => problem related to who gets to move queue pointers + spin_lock_init (&dev->cq.lock); + spin_lock_init (&dev->txq.lock); + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + spin_lock_init (&dev->rxq[pool].lock); +} + +static void setup_pci_dev(struct pci_dev *pci_dev) +{ + unsigned char lat; + + // enable bus master accesses + pci_set_master(pci_dev); + + // frobnicate latency (upwards, usually) + pci_read_config_byte (pci_dev, PCI_LATENCY_TIMER, &lat); + + if (!pci_lat) + pci_lat = (lat < MIN_PCI_LATENCY) ? MIN_PCI_LATENCY : lat; + + if (lat != pci_lat) { + PRINTK (KERN_INFO, "Changing PCI latency timer from %hu to %hu", + lat, pci_lat); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, pci_lat); + } +} + +static int amb_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_ent) +{ + amb_dev * dev; + int err; + unsigned int irq; + + err = pci_enable_device(pci_dev); + if (err < 0) { + PRINTK (KERN_ERR, "skipped broken (PLX rev 2) card"); + goto out; + } + + // read resources from PCI configuration space + irq = pci_dev->irq; + + if (pci_dev->device == PCI_DEVICE_ID_MADGE_AMBASSADOR_BAD) { + PRINTK (KERN_ERR, "skipped broken (PLX rev 2) card"); + err = -EINVAL; + goto out_disable; + } + + PRINTD (DBG_INFO, "found Madge ATM adapter (amb) at" + " IO %llx, IRQ %u, MEM %p", + (unsigned long long)pci_resource_start(pci_dev, 1), + irq, bus_to_virt(pci_resource_start(pci_dev, 0))); + + // check IO region + err = pci_request_region(pci_dev, 1, DEV_LABEL); + if (err < 0) { + PRINTK (KERN_ERR, "IO range already in use!"); + goto out_disable; + } + + dev = kzalloc(sizeof(amb_dev), GFP_KERNEL); + if (!dev) { + PRINTK (KERN_ERR, "out of memory!"); + err = -ENOMEM; + goto out_release; + } + + setup_dev(dev, pci_dev); + + err = amb_init(dev); + if (err < 0) { + PRINTK (KERN_ERR, "adapter initialisation failure"); + goto out_free; + } + + setup_pci_dev(pci_dev); + + // grab (but share) IRQ and install handler + err = request_irq(irq, interrupt_handler, IRQF_SHARED, DEV_LABEL, dev); + if (err < 0) { + PRINTK (KERN_ERR, "request IRQ failed!"); + goto out_reset; + } + + dev->atm_dev = atm_dev_register (DEV_LABEL, &pci_dev->dev, &amb_ops, -1, + NULL); + if (!dev->atm_dev) { + PRINTD (DBG_ERR, "failed to register Madge ATM adapter"); + err = -EINVAL; + goto out_free_irq; + } + + PRINTD (DBG_INFO, "registered Madge ATM adapter (no. %d) (%p) at %p", + dev->atm_dev->number, dev, dev->atm_dev); + dev->atm_dev->dev_data = (void *) dev; + + // register our address + amb_esi (dev, dev->atm_dev->esi); + + // 0 bits for vpi, 10 bits for vci + dev->atm_dev->ci_range.vpi_bits = NUM_VPI_BITS; + dev->atm_dev->ci_range.vci_bits = NUM_VCI_BITS; + + timer_setup(&dev->housekeeping, do_housekeeping, 0); + mod_timer(&dev->housekeeping, jiffies); + + // enable host interrupts + interrupts_on (dev); + +out: + return err; + +out_free_irq: + free_irq(irq, dev); +out_reset: + amb_reset(dev, 0); +out_free: + kfree(dev); +out_release: + pci_release_region(pci_dev, 1); +out_disable: + pci_disable_device(pci_dev); + goto out; +} + + +static void amb_remove_one(struct pci_dev *pci_dev) +{ + struct amb_dev *dev; + + dev = pci_get_drvdata(pci_dev); + + PRINTD(DBG_INFO|DBG_INIT, "closing %p (atm_dev = %p)", dev, dev->atm_dev); + del_timer_sync(&dev->housekeeping); + // the drain should not be necessary + drain_rx_pools(dev); + interrupts_off(dev); + amb_reset(dev, 0); + free_irq(dev->irq, dev); + pci_disable_device(pci_dev); + destroy_queues(dev); + atm_dev_deregister(dev->atm_dev); + kfree(dev); + pci_release_region(pci_dev, 1); +} + +static void __init amb_check_args (void) { + unsigned char pool; + unsigned int max_rx_size; + +#ifdef DEBUG_AMBASSADOR + PRINTK (KERN_NOTICE, "debug bitmap is %hx", debug &= DBG_MASK); +#else + if (debug) + PRINTK (KERN_NOTICE, "no debugging support"); +#endif + + if (cmds < MIN_QUEUE_SIZE) + PRINTK (KERN_NOTICE, "cmds has been raised to %u", + cmds = MIN_QUEUE_SIZE); + + if (txs < MIN_QUEUE_SIZE) + PRINTK (KERN_NOTICE, "txs has been raised to %u", + txs = MIN_QUEUE_SIZE); + + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + if (rxs[pool] < MIN_QUEUE_SIZE) + PRINTK (KERN_NOTICE, "rxs[%hu] has been raised to %u", + pool, rxs[pool] = MIN_QUEUE_SIZE); + + // buffers sizes should be greater than zero and strictly increasing + max_rx_size = 0; + for (pool = 0; pool < NUM_RX_POOLS; ++pool) + if (rxs_bs[pool] <= max_rx_size) + PRINTK (KERN_NOTICE, "useless pool (rxs_bs[%hu] = %u)", + pool, rxs_bs[pool]); + else + max_rx_size = rxs_bs[pool]; + + if (rx_lats < MIN_RX_BUFFERS) + PRINTK (KERN_NOTICE, "rx_lats has been raised to %u", + rx_lats = MIN_RX_BUFFERS); + + return; +} + +/********** module stuff **********/ + +MODULE_AUTHOR(maintainer_string); +MODULE_DESCRIPTION(description_string); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("atmsar11.fw"); +module_param(debug, ushort, 0644); +module_param(cmds, uint, 0); +module_param(txs, uint, 0); +module_param_array(rxs, uint, NULL, 0); +module_param_array(rxs_bs, uint, NULL, 0); +module_param(rx_lats, uint, 0); +module_param(pci_lat, byte, 0); +MODULE_PARM_DESC(debug, "debug bitmap, see .h file"); +MODULE_PARM_DESC(cmds, "number of command queue entries"); +MODULE_PARM_DESC(txs, "number of TX queue entries"); +MODULE_PARM_DESC(rxs, "number of RX queue entries [" __MODULE_STRING(NUM_RX_POOLS) "]"); +MODULE_PARM_DESC(rxs_bs, "size of RX buffers [" __MODULE_STRING(NUM_RX_POOLS) "]"); +MODULE_PARM_DESC(rx_lats, "number of extra buffers to cope with RX latencies"); +MODULE_PARM_DESC(pci_lat, "PCI latency in bus cycles"); + +/********** module entry **********/ + +static const struct pci_device_id amb_pci_tbl[] = { + { PCI_VDEVICE(MADGE, PCI_DEVICE_ID_MADGE_AMBASSADOR), 0 }, + { PCI_VDEVICE(MADGE, PCI_DEVICE_ID_MADGE_AMBASSADOR_BAD), 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, amb_pci_tbl); + +static struct pci_driver amb_driver = { + .name = "amb", + .probe = amb_probe, + .remove = amb_remove_one, + .id_table = amb_pci_tbl, +}; + +static int __init amb_module_init (void) +{ + PRINTD (DBG_FLOW|DBG_INIT, "init_module"); + + BUILD_BUG_ON(sizeof(amb_mem) != 4*16 + 4*12); + + show_version(); + + amb_check_args(); + + // get the juice + return pci_register_driver(&amb_driver); +} + +/********** module exit **********/ + +static void __exit amb_module_exit (void) +{ + PRINTD (DBG_FLOW|DBG_INIT, "cleanup_module"); + + pci_unregister_driver(&amb_driver); +} + +module_init(amb_module_init); +module_exit(amb_module_exit); diff --git a/drivers/atm/ambassador.h b/drivers/atm/ambassador.h new file mode 100644 index 000000000..086ceb856 --- /dev/null +++ b/drivers/atm/ambassador.h @@ -0,0 +1,648 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Madge Ambassador ATM Adapter driver. + Copyright (C) 1995-1999 Madge Networks Ltd. + +*/ + +#ifndef AMBASSADOR_H +#define AMBASSADOR_H + + +#ifdef CONFIG_ATM_AMBASSADOR_DEBUG +#define DEBUG_AMBASSADOR +#endif + +#define DEV_LABEL "amb" + +#ifndef PCI_VENDOR_ID_MADGE +#define PCI_VENDOR_ID_MADGE 0x10B6 +#endif +#ifndef PCI_VENDOR_ID_MADGE_AMBASSADOR +#define PCI_DEVICE_ID_MADGE_AMBASSADOR 0x1001 +#endif +#ifndef PCI_VENDOR_ID_MADGE_AMBASSADOR_BAD +#define PCI_DEVICE_ID_MADGE_AMBASSADOR_BAD 0x1002 +#endif + +// diagnostic output + +#define PRINTK(severity,format,args...) \ + printk(severity DEV_LABEL ": " format "\n" , ## args) + +#ifdef DEBUG_AMBASSADOR + +#define DBG_ERR 0x0001 +#define DBG_WARN 0x0002 +#define DBG_INFO 0x0004 +#define DBG_INIT 0x0008 +#define DBG_LOAD 0x0010 +#define DBG_VCC 0x0020 +#define DBG_QOS 0x0040 +#define DBG_CMD 0x0080 +#define DBG_TX 0x0100 +#define DBG_RX 0x0200 +#define DBG_SKB 0x0400 +#define DBG_POOL 0x0800 +#define DBG_IRQ 0x1000 +#define DBG_FLOW 0x2000 +#define DBG_REGS 0x4000 +#define DBG_DATA 0x8000 +#define DBG_MASK 0xffff + +/* the ## prevents the annoying double expansion of the macro arguments */ +/* KERN_INFO is used since KERN_DEBUG often does not make it to the console */ +#define PRINTDB(bits,format,args...) \ + ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format , ## args) : 1 ) +#define PRINTDM(bits,format,args...) \ + ( (debug & (bits)) ? printk (format , ## args) : 1 ) +#define PRINTDE(bits,format,args...) \ + ( (debug & (bits)) ? printk (format "\n" , ## args) : 1 ) +#define PRINTD(bits,format,args...) \ + ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format "\n" , ## args) : 1 ) + +#else + +#define PRINTD(bits,format,args...) +#define PRINTDB(bits,format,args...) +#define PRINTDM(bits,format,args...) +#define PRINTDE(bits,format,args...) + +#endif + +#define PRINTDD(bits,format,args...) +#define PRINTDDB(sec,fmt,args...) +#define PRINTDDM(sec,fmt,args...) +#define PRINTDDE(sec,fmt,args...) + +// tunable values (?) + +/* MUST be powers of two -- why ? */ +#define COM_Q_ENTRIES 8 +#define TX_Q_ENTRIES 32 +#define RX_Q_ENTRIES 64 + +// fixed values + +// guessing +#define AMB_EXTENT 0x80 + +// Minimum allowed size for an Ambassador queue +#define MIN_QUEUE_SIZE 2 + +// Ambassador microcode allows 1 to 4 pools, we use 4 (simpler) +#define NUM_RX_POOLS 4 + +// minimum RX buffers required to cope with replenishing delay +#define MIN_RX_BUFFERS 1 + +// minimum PCI latency we will tolerate (32 IS TOO SMALL) +#define MIN_PCI_LATENCY 64 // 255 + +// VCs supported by card (VPI always 0) +#define NUM_VPI_BITS 0 +#define NUM_VCI_BITS 10 +#define NUM_VCS 1024 + +/* The status field bits defined so far. */ +#define RX_ERR 0x8000 // always present if there is an error (hmm) +#define CRC_ERR 0x4000 // AAL5 CRC error +#define LEN_ERR 0x2000 // overlength frame +#define ABORT_ERR 0x1000 // zero length field in received frame +#define UNUSED_ERR 0x0800 // buffer returned unused + +// Adaptor commands + +#define SRB_OPEN_VC 0 +/* par_0: dwordswap(VC_number) */ +/* par_1: dwordswap(flags<<16) or wordswap(flags)*/ +/* flags: */ + +/* LANE: 0x0004 */ +/* NOT_UBR: 0x0008 */ +/* ABR: 0x0010 */ + +/* RxPool0: 0x0000 */ +/* RxPool1: 0x0020 */ +/* RxPool2: 0x0040 */ +/* RxPool3: 0x0060 */ + +/* par_2: dwordswap(fp_rate<<16) or wordswap(fp_rate) */ + +#define SRB_CLOSE_VC 1 +/* par_0: dwordswap(VC_number) */ + +#define SRB_GET_BIA 2 +/* returns */ +/* par_0: dwordswap(half BIA) */ +/* par_1: dwordswap(half BIA) */ + +#define SRB_GET_SUNI_STATS 3 +/* par_0: dwordswap(physical_host_address) */ + +#define SRB_SET_BITS_8 4 +#define SRB_SET_BITS_16 5 +#define SRB_SET_BITS_32 6 +#define SRB_CLEAR_BITS_8 7 +#define SRB_CLEAR_BITS_16 8 +#define SRB_CLEAR_BITS_32 9 +/* par_0: dwordswap(ATMizer address) */ +/* par_1: dwordswap(mask) */ + +#define SRB_SET_8 10 +#define SRB_SET_16 11 +#define SRB_SET_32 12 +/* par_0: dwordswap(ATMizer address) */ +/* par_1: dwordswap(data) */ + +#define SRB_GET_32 13 +/* par_0: dwordswap(ATMizer address) */ +/* returns */ +/* par_1: dwordswap(ATMizer data) */ + +#define SRB_GET_VERSION 14 +/* returns */ +/* par_0: dwordswap(Major Version) */ +/* par_1: dwordswap(Minor Version) */ + +#define SRB_FLUSH_BUFFER_Q 15 +/* Only flags to define which buffer pool; all others must be zero */ +/* par_0: dwordswap(flags<<16) or wordswap(flags)*/ + +#define SRB_GET_DMA_SPEEDS 16 +/* returns */ +/* par_0: dwordswap(Read speed (bytes/sec)) */ +/* par_1: dwordswap(Write speed (bytes/sec)) */ + +#define SRB_MODIFY_VC_RATE 17 +/* par_0: dwordswap(VC_number) */ +/* par_1: dwordswap(fp_rate<<16) or wordswap(fp_rate) */ + +#define SRB_MODIFY_VC_FLAGS 18 +/* par_0: dwordswap(VC_number) */ +/* par_1: dwordswap(flags<<16) or wordswap(flags)*/ + +/* flags: */ + +/* LANE: 0x0004 */ +/* NOT_UBR: 0x0008 */ +/* ABR: 0x0010 */ + +/* RxPool0: 0x0000 */ +/* RxPool1: 0x0020 */ +/* RxPool2: 0x0040 */ +/* RxPool3: 0x0060 */ + +#define SRB_RATE_SHIFT 16 +#define SRB_POOL_SHIFT (SRB_FLAGS_SHIFT+5) +#define SRB_FLAGS_SHIFT 16 + +#define SRB_STOP_TASKING 19 +#define SRB_START_TASKING 20 +#define SRB_SHUT_DOWN 21 +#define MAX_SRB 21 + +#define SRB_COMPLETE 0xffffffff + +#define TX_FRAME 0x80000000 + +// number of types of SRB MUST be a power of two -- why? +#define NUM_OF_SRB 32 + +// number of bits of period info for rate +#define MAX_RATE_BITS 6 + +#define TX_UBR 0x0000 +#define TX_UBR_CAPPED 0x0008 +#define TX_ABR 0x0018 +#define TX_FRAME_NOTCAP 0x0000 +#define TX_FRAME_CAPPED 0x8000 + +#define FP_155_RATE 0x24b1 +#define FP_25_RATE 0x1f9d + +/* #define VERSION_NUMBER 0x01000000 // initial release */ +/* #define VERSION_NUMBER 0x01010000 // fixed startup probs PLX MB0 not cleared */ +/* #define VERSION_NUMBER 0x01020000 // changed SUNI reset timings; allowed r/w onchip */ + +/* #define VERSION_NUMBER 0x01030000 // clear local doorbell int reg on reset */ +/* #define VERSION_NUMBER 0x01040000 // PLX bug work around version PLUS */ +/* remove race conditions on basic interface */ +/* indicate to the host that diagnostics */ +/* have finished; if failed, how and what */ +/* failed */ +/* fix host memory test to fix PLX bug */ +/* allow flash upgrade and BIA upgrade directly */ +/* */ +#define VERSION_NUMBER 0x01050025 /* Jason's first hacked version. */ +/* Change in download algorithm */ + +#define DMA_VALID 0xb728e149 /* completely random */ + +#define FLASH_BASE 0xa0c00000 +#define FLASH_SIZE 0x00020000 /* 128K */ +#define BIA_BASE (FLASH_BASE+0x0001c000) /* Flash Sector 7 */ +#define BIA_ADDRESS ((void *)0xa0c1c000) +#define PLX_BASE 0xe0000000 + +typedef enum { + host_memory_test = 1, + read_adapter_memory, + write_adapter_memory, + adapter_start, + get_version_number, + interrupt_host, + flash_erase_sector, + adap_download_block = 0x20, + adap_erase_flash, + adap_run_in_iram, + adap_end_download +} loader_command; + +#define BAD_COMMAND (-1) +#define COMMAND_IN_PROGRESS 1 +#define COMMAND_PASSED_TEST 2 +#define COMMAND_FAILED_TEST 3 +#define COMMAND_READ_DATA_OK 4 +#define COMMAND_READ_BAD_ADDRESS 5 +#define COMMAND_WRITE_DATA_OK 6 +#define COMMAND_WRITE_BAD_ADDRESS 7 +#define COMMAND_WRITE_FLASH_FAILURE 8 +#define COMMAND_COMPLETE 9 +#define COMMAND_FLASH_ERASE_FAILURE 10 +#define COMMAND_WRITE_BAD_DATA 11 + +/* bit fields for mailbox[0] return values */ + +#define GPINT_TST_FAILURE 0x00000001 +#define SUNI_DATA_PATTERN_FAILURE 0x00000002 +#define SUNI_DATA_BITS_FAILURE 0x00000004 +#define SUNI_UTOPIA_FAILURE 0x00000008 +#define SUNI_FIFO_FAILURE 0x00000010 +#define SRAM_FAILURE 0x00000020 +#define SELF_TEST_FAILURE 0x0000003f + +/* mailbox[1] = 0 in progress, -1 on completion */ +/* mailbox[2] = current test 00 00 test(8 bit) phase(8 bit) */ +/* mailbox[3] = last failure, 00 00 test(8 bit) phase(8 bit) */ +/* mailbox[4],mailbox[5],mailbox[6] random failure values */ + +/* PLX/etc. memory map including command structure */ + +/* These registers may also be memory mapped in PCI memory */ + +#define UNUSED_LOADER_MAILBOXES 6 + +typedef struct { + u32 stuff[16]; + union { + struct { + u32 result; + u32 ready; + u32 stuff[UNUSED_LOADER_MAILBOXES]; + } loader; + struct { + u32 cmd_address; + u32 tx_address; + u32 rx_address[NUM_RX_POOLS]; + u32 gen_counter; + u32 spare; + } adapter; + } mb; + u32 doorbell; + u32 interrupt; + u32 interrupt_control; + u32 reset_control; +} amb_mem; + +/* RESET bit, IRQ (card to host) and doorbell (host to card) enable bits */ +#define AMB_RESET_BITS 0x40000000 +#define AMB_INTERRUPT_BITS 0x00000300 +#define AMB_DOORBELL_BITS 0x00030000 + +/* loader commands */ + +#define MAX_COMMAND_DATA 13 +#define MAX_TRANSFER_DATA 11 + +typedef struct { + __be32 address; + __be32 count; + __be32 data[MAX_TRANSFER_DATA]; +} transfer_block; + +typedef struct { + __be32 result; + __be32 command; + union { + transfer_block transfer; + __be32 version; + __be32 start; + __be32 data[MAX_COMMAND_DATA]; + } payload; + __be32 valid; +} loader_block; + +/* command queue */ + +/* Again all data are BIG ENDIAN */ + +typedef struct { + union { + struct { + __be32 vc; + __be32 flags; + __be32 rate; + } open; + struct { + __be32 vc; + __be32 rate; + } modify_rate; + struct { + __be32 vc; + __be32 flags; + } modify_flags; + struct { + __be32 vc; + } close; + struct { + __be32 lower4; + __be32 upper2; + } bia; + struct { + __be32 address; + } suni; + struct { + __be32 major; + __be32 minor; + } version; + struct { + __be32 read; + __be32 write; + } speed; + struct { + __be32 flags; + } flush; + struct { + __be32 address; + __be32 data; + } memory; + __be32 par[3]; + } args; + __be32 request; +} command; + +/* transmit queues and associated structures */ + +/* The hosts transmit structure. All BIG ENDIAN; host address + restricted to first 1GByte, but address passed to the card must + have the top MS bit or'ed in. -- check this */ + +/* TX is described by 1+ tx_frags followed by a tx_frag_end */ + +typedef struct { + __be32 bytes; + __be32 address; +} tx_frag; + +/* apart from handle the fields here are for the adapter to play with + and should be set to zero */ + +typedef struct { + u32 handle; + u16 vc; + u16 next_descriptor_length; + u32 next_descriptor; +#ifdef AMB_NEW_MICROCODE + u8 cpcs_uu; + u8 cpi; + u16 pad; +#endif +} tx_frag_end; + +typedef struct { + tx_frag tx_frag; + tx_frag_end tx_frag_end; + struct sk_buff * skb; +} tx_simple; + +#if 0 +typedef union { + tx_frag fragment; + tx_frag_end end_of_list; +} tx_descr; +#endif + +/* this "points" to the sequence of fragments and trailer */ + +typedef struct { + __be16 vc; + __be16 tx_descr_length; + __be32 tx_descr_addr; +} tx_in; + +/* handle is the handle from tx_in */ + +typedef struct { + u32 handle; +} tx_out; + +/* receive frame structure */ + +/* All BIG ENDIAN; handle is as passed from host; length is zero for + aborted frames, and frames with errors. Header is actually VC + number, lec-id is NOT yet supported. */ + +typedef struct { + u32 handle; + __be16 vc; + __be16 lec_id; // unused + __be16 status; + __be16 length; +} rx_out; + +/* buffer supply structure */ + +typedef struct { + u32 handle; + __be32 host_address; +} rx_in; + +/* This first structure is the area in host memory where the adapter + writes its pointer values. These pointer values are BIG ENDIAN and + reside in the same 4MB 'page' as this structure. The host gives the + adapter the address of this block by sending a doorbell interrupt + to the adapter after downloading the code and setting it going. The + addresses have the top 10 bits set to 1010000010b -- really? + + The host must initialise these before handing the block to the + adapter. */ + +typedef struct { + __be32 command_start; /* SRB commands completions */ + __be32 command_end; /* SRB commands completions */ + __be32 tx_start; + __be32 tx_end; + __be32 txcom_start; /* tx completions */ + __be32 txcom_end; /* tx completions */ + struct { + __be32 buffer_start; + __be32 buffer_end; + u32 buffer_q_get; + u32 buffer_q_end; + u32 buffer_aptr; + __be32 rx_start; /* rx completions */ + __be32 rx_end; + u32 rx_ptr; + __be32 buffer_size; /* size of host buffer */ + } rec_struct[NUM_RX_POOLS]; +#ifdef AMB_NEW_MICROCODE + u16 init_flags; + u16 talk_block_spare; +#endif +} adap_talk_block; + +/* This structure must be kept in line with the vcr image in sarmain.h + + This is the structure in the host filled in by the adapter by + GET_SUNI_STATS */ + +typedef struct { + u8 racp_chcs; + u8 racp_uhcs; + u16 spare; + u32 racp_rcell; + u32 tacp_tcell; + u32 flags; + u32 dropped_cells; + u32 dropped_frames; +} suni_stats; + +typedef enum { + dead +} amb_flags; + +#define NEXTQ(current,start,limit) \ + ( (current)+1 < (limit) ? (current)+1 : (start) ) + +typedef struct { + command * start; + command * in; + command * out; + command * limit; +} amb_cq_ptrs; + +typedef struct { + spinlock_t lock; + unsigned int pending; + unsigned int high; + unsigned int filled; + unsigned int maximum; // size - 1 (q implementation) + amb_cq_ptrs ptrs; +} amb_cq; + +typedef struct { + spinlock_t lock; + unsigned int pending; + unsigned int high; + unsigned int filled; + unsigned int maximum; // size - 1 (q implementation) + struct { + tx_in * start; + tx_in * ptr; + tx_in * limit; + } in; + struct { + tx_out * start; + tx_out * ptr; + tx_out * limit; + } out; +} amb_txq; + +typedef struct { + spinlock_t lock; + unsigned int pending; + unsigned int low; + unsigned int emptied; + unsigned int maximum; // size - 1 (q implementation) + struct { + rx_in * start; + rx_in * ptr; + rx_in * limit; + } in; + struct { + rx_out * start; + rx_out * ptr; + rx_out * limit; + } out; + unsigned int buffers_wanted; + unsigned int buffer_size; +} amb_rxq; + +typedef struct { + unsigned long tx_ok; + struct { + unsigned long ok; + unsigned long error; + unsigned long badcrc; + unsigned long toolong; + unsigned long aborted; + unsigned long unused; + } rx; +} amb_stats; + +// a single struct pointed to by atm_vcc->dev_data + +typedef struct { + u8 tx_vc_bits:7; + u8 tx_present:1; +} amb_tx_info; + +typedef struct { + unsigned char pool; +} amb_rx_info; + +typedef struct { + amb_rx_info rx_info; + u16 tx_frame_bits; + unsigned int tx_rate; + unsigned int rx_rate; +} amb_vcc; + +struct amb_dev { + u8 irq; + unsigned long flags; + u32 iobase; + u32 * membase; + + amb_cq cq; + amb_txq txq; + amb_rxq rxq[NUM_RX_POOLS]; + + struct mutex vcc_sf; + amb_tx_info txer[NUM_VCS]; + struct atm_vcc * rxer[NUM_VCS]; + unsigned int tx_avail; + unsigned int rx_avail; + + amb_stats stats; + + struct atm_dev * atm_dev; + struct pci_dev * pci_dev; + struct timer_list housekeeping; +}; + +typedef struct amb_dev amb_dev; + +#define AMB_DEV(atm_dev) ((amb_dev *) (atm_dev)->dev_data) +#define AMB_VCC(atm_vcc) ((amb_vcc *) (atm_vcc)->dev_data) + +/* rate rounding */ + +typedef enum { + round_up, + round_down, + round_nearest +} rounding; + +#endif diff --git a/drivers/atm/atmtcp.c b/drivers/atm/atmtcp.c new file mode 100644 index 000000000..96bea1ab1 --- /dev/null +++ b/drivers/atm/atmtcp.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/atm/atmtcp.c - ATM over TCP "device" driver */ + +/* Written 1997-2000 by Werner Almesberger, EPFL LRC/ICA */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +extern int atm_init_aal5(struct atm_vcc *vcc); /* "raw" AAL5 transport */ + + +#define PRIV(dev) ((struct atmtcp_dev_data *) ((dev)->dev_data)) + + +struct atmtcp_dev_data { + struct atm_vcc *vcc; /* control VCC; NULL if detached */ + int persist; /* non-zero if persistent */ +}; + + +#define DEV_LABEL "atmtcp" + +#define MAX_VPI_BITS 8 /* simplifies life */ +#define MAX_VCI_BITS 16 + + +/* + * Hairy code ahead: the control VCC may be closed while we're still + * waiting for an answer, so we need to re-validate out_vcc every once + * in a while. + */ + + +static int atmtcp_send_control(struct atm_vcc *vcc,int type, + const struct atmtcp_control *msg,int flag) +{ + DECLARE_WAITQUEUE(wait,current); + struct atm_vcc *out_vcc; + struct sk_buff *skb; + struct atmtcp_control *new_msg; + int old_test; + int error = 0; + + out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL; + if (!out_vcc) return -EUNATCH; + skb = alloc_skb(sizeof(*msg),GFP_KERNEL); + if (!skb) return -ENOMEM; + mb(); + out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL; + if (!out_vcc) { + dev_kfree_skb(skb); + return -EUNATCH; + } + atm_force_charge(out_vcc,skb->truesize); + new_msg = skb_put(skb, sizeof(*new_msg)); + *new_msg = *msg; + new_msg->hdr.length = ATMTCP_HDR_MAGIC; + new_msg->type = type; + memset(&new_msg->vcc,0,sizeof(atm_kptr_t)); + *(struct atm_vcc **) &new_msg->vcc = vcc; + old_test = test_bit(flag,&vcc->flags); + out_vcc->push(out_vcc,skb); + add_wait_queue(sk_sleep(sk_atm(vcc)), &wait); + while (test_bit(flag,&vcc->flags) == old_test) { + mb(); + out_vcc = PRIV(vcc->dev) ? PRIV(vcc->dev)->vcc : NULL; + if (!out_vcc) { + error = -EUNATCH; + break; + } + set_current_state(TASK_UNINTERRUPTIBLE); + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk_sleep(sk_atm(vcc)), &wait); + return error; +} + + +static int atmtcp_recv_control(const struct atmtcp_control *msg) +{ + struct atm_vcc *vcc = *(struct atm_vcc **) &msg->vcc; + + vcc->vpi = msg->addr.sap_addr.vpi; + vcc->vci = msg->addr.sap_addr.vci; + vcc->qos = msg->qos; + sk_atm(vcc)->sk_err = -msg->result; + switch (msg->type) { + case ATMTCP_CTRL_OPEN: + change_bit(ATM_VF_READY,&vcc->flags); + break; + case ATMTCP_CTRL_CLOSE: + change_bit(ATM_VF_ADDR,&vcc->flags); + break; + default: + printk(KERN_ERR "atmtcp_recv_control: unknown type %d\n", + msg->type); + return -EINVAL; + } + wake_up(sk_sleep(sk_atm(vcc))); + return 0; +} + + +static void atmtcp_v_dev_close(struct atm_dev *dev) +{ + /* Nothing.... Isn't this simple :-) -- REW */ +} + + +static int atmtcp_v_open(struct atm_vcc *vcc) +{ + struct atmtcp_control msg; + int error; + short vpi = vcc->vpi; + int vci = vcc->vci; + + memset(&msg,0,sizeof(msg)); + msg.addr.sap_family = AF_ATMPVC; + msg.hdr.vpi = htons(vpi); + msg.addr.sap_addr.vpi = vpi; + msg.hdr.vci = htons(vci); + msg.addr.sap_addr.vci = vci; + if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) return 0; + msg.type = ATMTCP_CTRL_OPEN; + msg.qos = vcc->qos; + set_bit(ATM_VF_ADDR,&vcc->flags); + clear_bit(ATM_VF_READY,&vcc->flags); /* just in case ... */ + error = atmtcp_send_control(vcc,ATMTCP_CTRL_OPEN,&msg,ATM_VF_READY); + if (error) return error; + return -sk_atm(vcc)->sk_err; +} + + +static void atmtcp_v_close(struct atm_vcc *vcc) +{ + struct atmtcp_control msg; + + memset(&msg,0,sizeof(msg)); + msg.addr.sap_family = AF_ATMPVC; + msg.addr.sap_addr.vpi = vcc->vpi; + msg.addr.sap_addr.vci = vcc->vci; + clear_bit(ATM_VF_READY,&vcc->flags); + (void) atmtcp_send_control(vcc,ATMTCP_CTRL_CLOSE,&msg,ATM_VF_ADDR); +} + + +static int atmtcp_v_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + struct atm_cirange ci; + struct atm_vcc *vcc; + struct sock *s; + int i; + + if (cmd != ATM_SETCIRANGE) return -ENOIOCTLCMD; + if (copy_from_user(&ci, arg,sizeof(ci))) return -EFAULT; + if (ci.vpi_bits == ATM_CI_MAX) ci.vpi_bits = MAX_VPI_BITS; + if (ci.vci_bits == ATM_CI_MAX) ci.vci_bits = MAX_VCI_BITS; + if (ci.vpi_bits > MAX_VPI_BITS || ci.vpi_bits < 0 || + ci.vci_bits > MAX_VCI_BITS || ci.vci_bits < 0) return -EINVAL; + read_lock(&vcc_sklist_lock); + for(i = 0; i < VCC_HTABLE_SIZE; ++i) { + struct hlist_head *head = &vcc_hash[i]; + + sk_for_each(s, head) { + vcc = atm_sk(s); + if (vcc->dev != dev) + continue; + if ((vcc->vpi >> ci.vpi_bits) || + (vcc->vci >> ci.vci_bits)) { + read_unlock(&vcc_sklist_lock); + return -EBUSY; + } + } + } + read_unlock(&vcc_sklist_lock); + dev->ci_range = ci; + return 0; +} + + +static int atmtcp_v_send(struct atm_vcc *vcc,struct sk_buff *skb) +{ + struct atmtcp_dev_data *dev_data; + struct atm_vcc *out_vcc=NULL; /* Initializer quietens GCC warning */ + struct sk_buff *new_skb; + struct atmtcp_hdr *hdr; + int size; + + if (vcc->qos.txtp.traffic_class == ATM_NONE) { + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + return -EINVAL; + } + dev_data = PRIV(vcc->dev); + if (dev_data) out_vcc = dev_data->vcc; + if (!dev_data || !out_vcc) { + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + if (dev_data) return 0; + atomic_inc(&vcc->stats->tx_err); + return -ENOLINK; + } + size = skb->len+sizeof(struct atmtcp_hdr); + new_skb = atm_alloc_charge(out_vcc,size,GFP_ATOMIC); + if (!new_skb) { + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + atomic_inc(&vcc->stats->tx_err); + return -ENOBUFS; + } + hdr = skb_put(new_skb, sizeof(struct atmtcp_hdr)); + hdr->vpi = htons(vcc->vpi); + hdr->vci = htons(vcc->vci); + hdr->length = htonl(skb->len); + skb_copy_from_linear_data(skb, skb_put(new_skb, skb->len), skb->len); + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + out_vcc->push(out_vcc,new_skb); + atomic_inc(&vcc->stats->tx); + atomic_inc(&out_vcc->stats->rx); + return 0; +} + + +static int atmtcp_v_proc(struct atm_dev *dev,loff_t *pos,char *page) +{ + struct atmtcp_dev_data *dev_data = PRIV(dev); + + if (*pos) return 0; + if (!dev_data->persist) return sprintf(page,"ephemeral\n"); + return sprintf(page,"persistent, %sconnected\n", + dev_data->vcc ? "" : "dis"); +} + + +static void atmtcp_c_close(struct atm_vcc *vcc) +{ + struct atm_dev *atmtcp_dev; + struct atmtcp_dev_data *dev_data; + + atmtcp_dev = (struct atm_dev *) vcc->dev_data; + dev_data = PRIV(atmtcp_dev); + dev_data->vcc = NULL; + if (dev_data->persist) return; + atmtcp_dev->dev_data = NULL; + kfree(dev_data); + atm_dev_deregister(atmtcp_dev); + vcc->dev_data = NULL; + module_put(THIS_MODULE); +} + + +static struct atm_vcc *find_vcc(struct atm_dev *dev, short vpi, int vci) +{ + struct hlist_head *head; + struct atm_vcc *vcc; + struct sock *s; + + head = &vcc_hash[vci & (VCC_HTABLE_SIZE -1)]; + + sk_for_each(s, head) { + vcc = atm_sk(s); + if (vcc->dev == dev && + vcc->vci == vci && vcc->vpi == vpi && + vcc->qos.rxtp.traffic_class != ATM_NONE) { + return vcc; + } + } + return NULL; +} + + +static int atmtcp_c_send(struct atm_vcc *vcc,struct sk_buff *skb) +{ + struct atm_dev *dev; + struct atmtcp_hdr *hdr; + struct atm_vcc *out_vcc; + struct sk_buff *new_skb; + int result = 0; + + if (!skb->len) return 0; + dev = vcc->dev_data; + hdr = (struct atmtcp_hdr *) skb->data; + if (hdr->length == ATMTCP_HDR_MAGIC) { + result = atmtcp_recv_control( + (struct atmtcp_control *) skb->data); + goto done; + } + read_lock(&vcc_sklist_lock); + out_vcc = find_vcc(dev, ntohs(hdr->vpi), ntohs(hdr->vci)); + read_unlock(&vcc_sklist_lock); + if (!out_vcc) { + result = -EUNATCH; + atomic_inc(&vcc->stats->tx_err); + goto done; + } + skb_pull(skb,sizeof(struct atmtcp_hdr)); + new_skb = atm_alloc_charge(out_vcc,skb->len,GFP_KERNEL); + if (!new_skb) { + result = -ENOBUFS; + goto done; + } + __net_timestamp(new_skb); + skb_copy_from_linear_data(skb, skb_put(new_skb, skb->len), skb->len); + out_vcc->push(out_vcc,new_skb); + atomic_inc(&vcc->stats->tx); + atomic_inc(&out_vcc->stats->rx); +done: + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + return result; +} + + +/* + * Device operations for the virtual ATM devices created by ATMTCP. + */ + + +static const struct atmdev_ops atmtcp_v_dev_ops = { + .dev_close = atmtcp_v_dev_close, + .open = atmtcp_v_open, + .close = atmtcp_v_close, + .ioctl = atmtcp_v_ioctl, + .send = atmtcp_v_send, + .proc_read = atmtcp_v_proc, + .owner = THIS_MODULE +}; + + +/* + * Device operations for the ATMTCP control device. + */ + + +static const struct atmdev_ops atmtcp_c_dev_ops = { + .close = atmtcp_c_close, + .send = atmtcp_c_send +}; + + +static struct atm_dev atmtcp_control_dev = { + .ops = &atmtcp_c_dev_ops, + .type = "atmtcp", + .number = 999, + .lock = __SPIN_LOCK_UNLOCKED(atmtcp_control_dev.lock) +}; + + +static int atmtcp_create(int itf,int persist,struct atm_dev **result) +{ + struct atmtcp_dev_data *dev_data; + struct atm_dev *dev; + + dev_data = kmalloc(sizeof(*dev_data),GFP_KERNEL); + if (!dev_data) + return -ENOMEM; + + dev = atm_dev_register(DEV_LABEL,NULL,&atmtcp_v_dev_ops,itf,NULL); + if (!dev) { + kfree(dev_data); + return itf == -1 ? -ENOMEM : -EBUSY; + } + dev->ci_range.vpi_bits = MAX_VPI_BITS; + dev->ci_range.vci_bits = MAX_VCI_BITS; + dev->dev_data = dev_data; + PRIV(dev)->vcc = NULL; + PRIV(dev)->persist = persist; + if (result) *result = dev; + return 0; +} + + +static int atmtcp_attach(struct atm_vcc *vcc,int itf) +{ + struct atm_dev *dev; + + dev = NULL; + if (itf != -1) dev = atm_dev_lookup(itf); + if (dev) { + if (dev->ops != &atmtcp_v_dev_ops) { + atm_dev_put(dev); + return -EMEDIUMTYPE; + } + if (PRIV(dev)->vcc) { + atm_dev_put(dev); + return -EBUSY; + } + } + else { + int error; + + error = atmtcp_create(itf,0,&dev); + if (error) return error; + } + PRIV(dev)->vcc = vcc; + vcc->dev = &atmtcp_control_dev; + vcc_insert_socket(sk_atm(vcc)); + set_bit(ATM_VF_META,&vcc->flags); + set_bit(ATM_VF_READY,&vcc->flags); + vcc->dev_data = dev; + (void) atm_init_aal5(vcc); /* @@@ losing AAL in transit ... */ + vcc->stats = &atmtcp_control_dev.stats.aal5; + return dev->number; +} + + +static int atmtcp_create_persistent(int itf) +{ + return atmtcp_create(itf,1,NULL); +} + + +static int atmtcp_remove_persistent(int itf) +{ + struct atm_dev *dev; + struct atmtcp_dev_data *dev_data; + + dev = atm_dev_lookup(itf); + if (!dev) return -ENODEV; + if (dev->ops != &atmtcp_v_dev_ops) { + atm_dev_put(dev); + return -EMEDIUMTYPE; + } + dev_data = PRIV(dev); + if (!dev_data->persist) { + atm_dev_put(dev); + return 0; + } + dev_data->persist = 0; + if (PRIV(dev)->vcc) { + atm_dev_put(dev); + return 0; + } + kfree(dev_data); + atm_dev_put(dev); + atm_dev_deregister(dev); + return 0; +} + +static int atmtcp_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct atm_vcc *vcc = ATM_SD(sock); + + if (cmd != SIOCSIFATMTCP && cmd != ATMTCP_CREATE && cmd != ATMTCP_REMOVE) + return -ENOIOCTLCMD; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + switch (cmd) { + case SIOCSIFATMTCP: + err = atmtcp_attach(vcc, (int) arg); + if (err >= 0) { + sock->state = SS_CONNECTED; + __module_get(THIS_MODULE); + } + break; + case ATMTCP_CREATE: + err = atmtcp_create_persistent((int) arg); + break; + case ATMTCP_REMOVE: + err = atmtcp_remove_persistent((int) arg); + break; + } + return err; +} + +static struct atm_ioctl atmtcp_ioctl_ops = { + .owner = THIS_MODULE, + .ioctl = atmtcp_ioctl, +}; + +static __init int atmtcp_init(void) +{ + register_atm_ioctl(&atmtcp_ioctl_ops); + return 0; +} + + +static void __exit atmtcp_exit(void) +{ + deregister_atm_ioctl(&atmtcp_ioctl_ops); +} + +MODULE_LICENSE("GPL"); +module_init(atmtcp_init); +module_exit(atmtcp_exit); diff --git a/drivers/atm/eni.c b/drivers/atm/eni.c new file mode 100644 index 000000000..9fcc49be4 --- /dev/null +++ b/drivers/atm/eni.c @@ -0,0 +1,2321 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/atm/eni.c - Efficient Networks ENI155P device driver */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tonga.h" +#include "midway.h" +#include "suni.h" +#include "eni.h" + +/* + * TODO: + * + * Show stoppers + * none + * + * Minor + * - OAM support + * - fix bugs listed below + */ + +/* + * KNOWN BUGS: + * + * - may run into JK-JK bug and deadlock + * - should allocate UBR channel first + * - buffer space allocation algorithm is stupid + * (RX: should be maxSDU+maxdelay*rate + * TX: should be maxSDU+min(maxSDU,maxdelay*rate) ) + * - doesn't support OAM cells + * - eni_put_free may hang if not putting memory fragments that _complete_ + * 2^n block (never happens in real life, though) + */ + + +#if 0 +#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) +#else +#define DPRINTK(format,args...) +#endif + + +#ifndef CONFIG_ATM_ENI_TUNE_BURST +#define CONFIG_ATM_ENI_BURST_TX_8W +#define CONFIG_ATM_ENI_BURST_RX_4W +#endif + + +#ifndef CONFIG_ATM_ENI_DEBUG + + +#define NULLCHECK(x) + +#define EVENT(s,a,b) + + +static void event_dump(void) +{ +} + + +#else + + +/* + * NULL pointer checking + */ + +#define NULLCHECK(x) \ + if ((unsigned long) (x) < 0x30) \ + printk(KERN_CRIT #x "==0x%lx\n",(unsigned long) (x)) + +/* + * Very extensive activity logging. Greatly improves bug detection speed but + * costs a few Mbps if enabled. + */ + +#define EV 64 + +static const char *ev[EV]; +static unsigned long ev_a[EV],ev_b[EV]; +static int ec = 0; + + +static void EVENT(const char *s,unsigned long a,unsigned long b) +{ + ev[ec] = s; + ev_a[ec] = a; + ev_b[ec] = b; + ec = (ec+1) % EV; +} + + +static void event_dump(void) +{ + int n,i; + + for (n = 0; n < EV; n++) { + i = (ec+n) % EV; + printk(KERN_NOTICE); + printk(ev[i] ? ev[i] : "(null)",ev_a[i],ev_b[i]); + } +} + + +#endif /* CONFIG_ATM_ENI_DEBUG */ + + +/* + * NExx must not be equal at end + * EExx may be equal at end + * xxPJOK verify validity of pointer jumps + * xxPMOK operating on a circular buffer of "c" words + */ + +#define NEPJOK(a0,a1,b) \ + ((a0) < (a1) ? (b) <= (a0) || (b) > (a1) : (b) <= (a0) && (b) > (a1)) +#define EEPJOK(a0,a1,b) \ + ((a0) < (a1) ? (b) < (a0) || (b) >= (a1) : (b) < (a0) && (b) >= (a1)) +#define NEPMOK(a0,d,b,c) NEPJOK(a0,(a0+d) & (c-1),b) +#define EEPMOK(a0,d,b,c) EEPJOK(a0,(a0+d) & (c-1),b) + + +static int tx_complete = 0,dma_complete = 0,queued = 0,requeued = 0, + backlogged = 0,rx_enqueued = 0,rx_dequeued = 0,pushed = 0,submitted = 0, + putting = 0; + +static struct atm_dev *eni_boards = NULL; + +/* Read/write registers on card */ +#define eni_in(r) readl(eni_dev->reg+(r)*4) +#define eni_out(v,r) writel((v),eni_dev->reg+(r)*4) + + +/*-------------------------------- utilities --------------------------------*/ + + +static void dump_mem(struct eni_dev *eni_dev) +{ + int i; + + for (i = 0; i < eni_dev->free_len; i++) + printk(KERN_DEBUG " %d: %p %d\n",i, + eni_dev->free_list[i].start, + 1 << eni_dev->free_list[i].order); +} + + +static void dump(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + + int i; + + eni_dev = ENI_DEV(dev); + printk(KERN_NOTICE "Free memory\n"); + dump_mem(eni_dev); + printk(KERN_NOTICE "TX buffers\n"); + for (i = 0; i < NR_CHAN; i++) + if (eni_dev->tx[i].send) + printk(KERN_NOTICE " TX %d @ %p: %ld\n",i, + eni_dev->tx[i].send,eni_dev->tx[i].words*4); + printk(KERN_NOTICE "RX buffers\n"); + for (i = 0; i < 1024; i++) + if (eni_dev->rx_map[i] && ENI_VCC(eni_dev->rx_map[i])->rx) + printk(KERN_NOTICE " RX %d @ %p: %ld\n",i, + ENI_VCC(eni_dev->rx_map[i])->recv, + ENI_VCC(eni_dev->rx_map[i])->words*4); + printk(KERN_NOTICE "----\n"); +} + + +static void eni_put_free(struct eni_dev *eni_dev, void __iomem *start, + unsigned long size) +{ + struct eni_free *list; + int len,order; + + DPRINTK("init 0x%lx+%ld(0x%lx)\n",start,size,size); + start += eni_dev->base_diff; + list = eni_dev->free_list; + len = eni_dev->free_len; + while (size) { + if (len >= eni_dev->free_list_size) { + printk(KERN_CRIT "eni_put_free overflow (%p,%ld)\n", + start,size); + break; + } + for (order = 0; !(((unsigned long)start | size) & (1 << order)); order++); + if (MID_MIN_BUF_SIZE > (1 << order)) { + printk(KERN_CRIT "eni_put_free: order %d too small\n", + order); + break; + } + list[len].start = (void __iomem *) start; + list[len].order = order; + len++; + start += 1 << order; + size -= 1 << order; + } + eni_dev->free_len = len; + /*dump_mem(eni_dev);*/ +} + + +static void __iomem *eni_alloc_mem(struct eni_dev *eni_dev, unsigned long *size) +{ + struct eni_free *list; + void __iomem *start; + int len,i,order,best_order,index; + + list = eni_dev->free_list; + len = eni_dev->free_len; + if (*size < MID_MIN_BUF_SIZE) *size = MID_MIN_BUF_SIZE; + if (*size > MID_MAX_BUF_SIZE) return NULL; + for (order = 0; (1 << order) < *size; order++) + ; + DPRINTK("trying: %ld->%d\n",*size,order); + best_order = 65; /* we don't have more than 2^64 of anything ... */ + index = 0; /* silence GCC */ + for (i = 0; i < len; i++) + if (list[i].order == order) { + best_order = order; + index = i; + break; + } + else if (best_order > list[i].order && list[i].order > order) { + best_order = list[i].order; + index = i; + } + if (best_order == 65) return NULL; + start = list[index].start-eni_dev->base_diff; + list[index] = list[--len]; + eni_dev->free_len = len; + *size = 1 << order; + eni_put_free(eni_dev,start+*size,(1 << best_order)-*size); + DPRINTK("%ld bytes (order %d) at 0x%lx\n",*size,order,start); + memset_io(start,0,*size); /* never leak data */ + /*dump_mem(eni_dev);*/ + return start; +} + + +static void eni_free_mem(struct eni_dev *eni_dev, void __iomem *start, + unsigned long size) +{ + struct eni_free *list; + int len,i,order; + + start += eni_dev->base_diff; + list = eni_dev->free_list; + len = eni_dev->free_len; + for (order = -1; size; order++) size >>= 1; + DPRINTK("eni_free_mem: %p+0x%lx (order %d)\n",start,size,order); + for (i = 0; i < len; i++) + if (((unsigned long) list[i].start) == ((unsigned long)start^(1 << order)) && + list[i].order == order) { + DPRINTK("match[%d]: 0x%lx/0x%lx(0x%x), %d/%d\n",i, + list[i].start,start,1 << order,list[i].order,order); + list[i] = list[--len]; + start = (void __iomem *) ((unsigned long) start & ~(unsigned long) (1 << order)); + order++; + i = -1; + continue; + } + if (len >= eni_dev->free_list_size) { + printk(KERN_ALERT "eni_free_mem overflow (%p,%d)\n",start, + order); + return; + } + list[len].start = start; + list[len].order = order; + eni_dev->free_len = len+1; + /*dump_mem(eni_dev);*/ +} + + +/*----------------------------------- RX ------------------------------------*/ + + +#define ENI_VCC_NOS ((struct atm_vcc *) 1) + + +static void rx_ident_err(struct atm_vcc *vcc) +{ + struct atm_dev *dev; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + + dev = vcc->dev; + eni_dev = ENI_DEV(dev); + /* immediately halt adapter */ + eni_out(eni_in(MID_MC_S) & + ~(MID_DMA_ENABLE | MID_TX_ENABLE | MID_RX_ENABLE),MID_MC_S); + /* dump useful information */ + eni_vcc = ENI_VCC(vcc); + printk(KERN_ALERT DEV_LABEL "(itf %d): driver error - RX ident " + "mismatch\n",dev->number); + printk(KERN_ALERT " VCI %d, rxing %d, words %ld\n",vcc->vci, + eni_vcc->rxing,eni_vcc->words); + printk(KERN_ALERT " host descr 0x%lx, rx pos 0x%lx, descr value " + "0x%x\n",eni_vcc->descr,eni_vcc->rx_pos, + (unsigned) readl(eni_vcc->recv+eni_vcc->descr*4)); + printk(KERN_ALERT " last %p, servicing %d\n",eni_vcc->last, + eni_vcc->servicing); + EVENT("---dump ends here---\n",0,0); + printk(KERN_NOTICE "---recent events---\n"); + event_dump(); + ENI_DEV(dev)->fast = NULL; /* really stop it */ + ENI_DEV(dev)->slow = NULL; + skb_queue_head_init(&ENI_DEV(dev)->rx_queue); +} + + +static int do_rx_dma(struct atm_vcc *vcc,struct sk_buff *skb, + unsigned long skip,unsigned long size,unsigned long eff) +{ + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + u32 dma_rd,dma_wr; + u32 dma[RX_DMA_BUF*2]; + dma_addr_t paddr; + unsigned long here; + int i,j; + + eni_dev = ENI_DEV(vcc->dev); + eni_vcc = ENI_VCC(vcc); + paddr = 0; /* GCC, shut up */ + if (skb) { + paddr = dma_map_single(&eni_dev->pci_dev->dev,skb->data,skb->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(&eni_dev->pci_dev->dev, paddr)) + goto dma_map_error; + ENI_PRV_PADDR(skb) = paddr; + if (paddr & 3) + printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %d has " + "mis-aligned RX data (0x%lx)\n",vcc->dev->number, + vcc->vci,(unsigned long) paddr); + ENI_PRV_SIZE(skb) = size+skip; + /* PDU plus descriptor */ + ATM_SKB(skb)->vcc = vcc; + } + j = 0; + if ((eff && skip) || 1) { /* @@@ actually, skip is always == 1 ... */ + here = (eni_vcc->descr+skip) & (eni_vcc->words-1); + dma[j++] = (here << MID_DMA_COUNT_SHIFT) | (vcc->vci + << MID_DMA_VCI_SHIFT) | MID_DT_JK; + dma[j++] = 0; + } + here = (eni_vcc->descr+size+skip) & (eni_vcc->words-1); + if (!eff) size += skip; + else { + unsigned long words; + + if (!size) { + DPRINTK("strange things happen ...\n"); + EVENT("strange things happen ... (skip=%ld,eff=%ld)\n", + size,eff); + } + words = eff; + if (paddr & 15) { + unsigned long init; + + init = 4-((paddr & 15) >> 2); + if (init > words) init = words; + dma[j++] = MID_DT_WORD | (init << MID_DMA_COUNT_SHIFT) | + (vcc->vci << MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += init << 2; + words -= init; + } +#ifdef CONFIG_ATM_ENI_BURST_RX_16W /* may work with some PCI chipsets ... */ + if (words & ~15) { + dma[j++] = MID_DT_16W | ((words >> 4) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~15) << 2; + words &= 15; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_8W /* works only with *some* PCI chipsets ... */ + if (words & ~7) { + dma[j++] = MID_DT_8W | ((words >> 3) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~7) << 2; + words &= 7; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_4W /* recommended */ + if (words & ~3) { + dma[j++] = MID_DT_4W | ((words >> 2) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~3) << 2; + words &= 3; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_2W /* probably useless if RX_4W, RX_8W, ... */ + if (words & ~1) { + dma[j++] = MID_DT_2W | ((words >> 1) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~1) << 2; + words &= 1; + } +#endif + if (words) { + dma[j++] = MID_DT_WORD | (words << MID_DMA_COUNT_SHIFT) + | (vcc->vci << MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + } + } + if (size != eff) { + dma[j++] = (here << MID_DMA_COUNT_SHIFT) | + (vcc->vci << MID_DMA_VCI_SHIFT) | MID_DT_JK; + dma[j++] = 0; + } + if (!j || j > 2*RX_DMA_BUF) { + printk(KERN_CRIT DEV_LABEL "!j or j too big!!!\n"); + goto trouble; + } + dma[j-2] |= MID_DMA_END; + j = j >> 1; + dma_wr = eni_in(MID_DMA_WR_RX); + dma_rd = eni_in(MID_DMA_RD_RX); + /* + * Can I move the dma_wr pointer by 2j+1 positions without overwriting + * data that hasn't been read (position of dma_rd) yet ? + */ + if (!NEPMOK(dma_wr,j+j+1,dma_rd,NR_DMA_RX)) { /* @@@ +1 is ugly */ + printk(KERN_WARNING DEV_LABEL "(itf %d): RX DMA full\n", + vcc->dev->number); + goto trouble; + } + for (i = 0; i < j; i++) { + writel(dma[i*2],eni_dev->rx_dma+dma_wr*8); + writel(dma[i*2+1],eni_dev->rx_dma+dma_wr*8+4); + dma_wr = (dma_wr+1) & (NR_DMA_RX-1); + } + if (skb) { + ENI_PRV_POS(skb) = eni_vcc->descr+size+1; + skb_queue_tail(&eni_dev->rx_queue,skb); + eni_vcc->last = skb; + rx_enqueued++; + } + eni_vcc->descr = here; + eni_out(dma_wr,MID_DMA_WR_RX); + return 0; + +trouble: + if (paddr) + dma_unmap_single(&eni_dev->pci_dev->dev,paddr,skb->len, + DMA_FROM_DEVICE); +dma_map_error: + if (skb) dev_kfree_skb_irq(skb); + return -1; +} + + +static void discard(struct atm_vcc *vcc,unsigned long size) +{ + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + EVENT("discard (size=%ld)\n",size,0); + while (do_rx_dma(vcc,NULL,1,size,0)) EVENT("BUSY LOOP",0,0); + /* could do a full fallback, but that might be more expensive */ + if (eni_vcc->rxing) ENI_PRV_POS(eni_vcc->last) += size+1; + else eni_vcc->rx_pos = (eni_vcc->rx_pos+size+1) & (eni_vcc->words-1); +} + + +/* + * TODO: should check whether direct copies (without DMA setup, dequeuing on + * interrupt, etc.) aren't much faster for AAL0 + */ + +static int rx_aal0(struct atm_vcc *vcc) +{ + struct eni_vcc *eni_vcc; + unsigned long descr; + unsigned long length; + struct sk_buff *skb; + + DPRINTK(">rx_aal0\n"); + eni_vcc = ENI_VCC(vcc); + descr = readl(eni_vcc->recv+eni_vcc->descr*4); + if ((descr & MID_RED_IDEN) != (MID_RED_RX_ID << MID_RED_SHIFT)) { + rx_ident_err(vcc); + return 1; + } + if (descr & MID_RED_T) { + DPRINTK(DEV_LABEL "(itf %d): trashing empty cell\n", + vcc->dev->number); + length = 0; + atomic_inc(&vcc->stats->rx_err); + } + else { + length = ATM_CELL_SIZE-1; /* no HEC */ + } + skb = length ? atm_alloc_charge(vcc,length,GFP_ATOMIC) : NULL; + if (!skb) { + discard(vcc,length >> 2); + return 0; + } + skb_put(skb,length); + skb->tstamp = eni_vcc->timestamp; + DPRINTK("got len %ld\n",length); + if (do_rx_dma(vcc,skb,1,length >> 2,length >> 2)) return 1; + eni_vcc->rxing++; + return 0; +} + + +static int rx_aal5(struct atm_vcc *vcc) +{ + struct eni_vcc *eni_vcc; + unsigned long descr; + unsigned long size,eff,length; + struct sk_buff *skb; + + EVENT("rx_aal5\n",0,0); + DPRINTK(">rx_aal5\n"); + eni_vcc = ENI_VCC(vcc); + descr = readl(eni_vcc->recv+eni_vcc->descr*4); + if ((descr & MID_RED_IDEN) != (MID_RED_RX_ID << MID_RED_SHIFT)) { + rx_ident_err(vcc); + return 1; + } + if (descr & (MID_RED_T | MID_RED_CRC_ERR)) { + if (descr & MID_RED_T) { + EVENT("empty cell (descr=0x%lx)\n",descr,0); + DPRINTK(DEV_LABEL "(itf %d): trashing empty cell\n", + vcc->dev->number); + size = 0; + } + else { + static unsigned long silence = 0; + + if (time_after(jiffies, silence) || silence == 0) { + printk(KERN_WARNING DEV_LABEL "(itf %d): " + "discarding PDU(s) with CRC error\n", + vcc->dev->number); + silence = (jiffies+2*HZ)|1; + } + size = (descr & MID_RED_COUNT)*(ATM_CELL_PAYLOAD >> 2); + EVENT("CRC error (descr=0x%lx,size=%ld)\n",descr, + size); + } + eff = length = 0; + atomic_inc(&vcc->stats->rx_err); + } + else { + size = (descr & MID_RED_COUNT)*(ATM_CELL_PAYLOAD >> 2); + DPRINTK("size=%ld\n",size); + length = readl(eni_vcc->recv+(((eni_vcc->descr+size-1) & + (eni_vcc->words-1)))*4) & 0xffff; + /* -trailer(2)+header(1) */ + if (length && length <= (size << 2)-8 && length <= + ATM_MAX_AAL5_PDU) eff = (length+3) >> 2; + else { /* ^ trailer length (8) */ + EVENT("bad PDU (descr=0x08%lx,length=%ld)\n",descr, + length); + printk(KERN_ERR DEV_LABEL "(itf %d): bad AAL5 PDU " + "(VCI=%d,length=%ld,size=%ld (descr 0x%lx))\n", + vcc->dev->number,vcc->vci,length,size << 2,descr); + length = eff = 0; + atomic_inc(&vcc->stats->rx_err); + } + } + skb = eff ? atm_alloc_charge(vcc,eff << 2,GFP_ATOMIC) : NULL; + if (!skb) { + discard(vcc,size); + return 0; + } + skb_put(skb,length); + DPRINTK("got len %ld\n",length); + if (do_rx_dma(vcc,skb,1,size,eff)) return 1; + eni_vcc->rxing++; + return 0; +} + + +static inline int rx_vcc(struct atm_vcc *vcc) +{ + void __iomem *vci_dsc; + unsigned long tmp; + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + vci_dsc = ENI_DEV(vcc->dev)->vci+vcc->vci*16; + EVENT("rx_vcc(1)\n",0,0); + while (eni_vcc->descr != (tmp = (readl(vci_dsc+4) & MID_VCI_DESCR) >> + MID_VCI_DESCR_SHIFT)) { + EVENT("rx_vcc(2: host dsc=0x%lx, nic dsc=0x%lx)\n", + eni_vcc->descr,tmp); + DPRINTK("CB_DESCR %ld REG_DESCR %d\n",ENI_VCC(vcc)->descr, + (((unsigned) readl(vci_dsc+4) & MID_VCI_DESCR) >> + MID_VCI_DESCR_SHIFT)); + if (ENI_VCC(vcc)->rx(vcc)) return 1; + } + /* clear IN_SERVICE flag */ + writel(readl(vci_dsc) & ~MID_VCI_IN_SERVICE,vci_dsc); + /* + * If new data has arrived between evaluating the while condition and + * clearing IN_SERVICE, we wouldn't be notified until additional data + * follows. So we have to loop again to be sure. + */ + EVENT("rx_vcc(3)\n",0,0); + while (ENI_VCC(vcc)->descr != (tmp = (readl(vci_dsc+4) & MID_VCI_DESCR) + >> MID_VCI_DESCR_SHIFT)) { + EVENT("rx_vcc(4: host dsc=0x%lx, nic dsc=0x%lx)\n", + eni_vcc->descr,tmp); + DPRINTK("CB_DESCR %ld REG_DESCR %d\n",ENI_VCC(vcc)->descr, + (((unsigned) readl(vci_dsc+4) & MID_VCI_DESCR) >> + MID_VCI_DESCR_SHIFT)); + if (ENI_VCC(vcc)->rx(vcc)) return 1; + } + return 0; +} + + +static void poll_rx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct atm_vcc *curr; + + eni_dev = ENI_DEV(dev); + while ((curr = eni_dev->fast)) { + EVENT("poll_rx.fast\n",0,0); + if (rx_vcc(curr)) return; + eni_dev->fast = ENI_VCC(curr)->next; + ENI_VCC(curr)->next = ENI_VCC_NOS; + barrier(); + ENI_VCC(curr)->servicing--; + } + while ((curr = eni_dev->slow)) { + EVENT("poll_rx.slow\n",0,0); + if (rx_vcc(curr)) return; + eni_dev->slow = ENI_VCC(curr)->next; + ENI_VCC(curr)->next = ENI_VCC_NOS; + barrier(); + ENI_VCC(curr)->servicing--; + } +} + + +static void get_service(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct atm_vcc *vcc; + unsigned long vci; + + DPRINTK(">get_service\n"); + eni_dev = ENI_DEV(dev); + while (eni_in(MID_SERV_WRITE) != eni_dev->serv_read) { + vci = readl(eni_dev->service+eni_dev->serv_read*4); + eni_dev->serv_read = (eni_dev->serv_read+1) & (NR_SERVICE-1); + vcc = eni_dev->rx_map[vci & 1023]; + if (!vcc) { + printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %ld not " + "found\n",dev->number,vci); + continue; /* nasty but we try to go on anyway */ + /* @@@ nope, doesn't work */ + } + EVENT("getting from service\n",0,0); + if (ENI_VCC(vcc)->next != ENI_VCC_NOS) { + EVENT("double service\n",0,0); + DPRINTK("Grr, servicing VCC %ld twice\n",vci); + continue; + } + ENI_VCC(vcc)->timestamp = ktime_get_real(); + ENI_VCC(vcc)->next = NULL; + if (vcc->qos.rxtp.traffic_class == ATM_CBR) { + if (eni_dev->fast) + ENI_VCC(eni_dev->last_fast)->next = vcc; + else eni_dev->fast = vcc; + eni_dev->last_fast = vcc; + } + else { + if (eni_dev->slow) + ENI_VCC(eni_dev->last_slow)->next = vcc; + else eni_dev->slow = vcc; + eni_dev->last_slow = vcc; + } + putting++; + ENI_VCC(vcc)->servicing++; + } +} + + +static void dequeue_rx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + struct atm_vcc *vcc; + struct sk_buff *skb; + void __iomem *vci_dsc; + int first; + + eni_dev = ENI_DEV(dev); + first = 1; + while (1) { + skb = skb_dequeue(&eni_dev->rx_queue); + if (!skb) { + if (first) { + DPRINTK(DEV_LABEL "(itf %d): RX but not " + "rxing\n",dev->number); + EVENT("nothing to dequeue\n",0,0); + } + break; + } + EVENT("dequeued (size=%ld,pos=0x%lx)\n",ENI_PRV_SIZE(skb), + ENI_PRV_POS(skb)); + rx_dequeued++; + vcc = ATM_SKB(skb)->vcc; + eni_vcc = ENI_VCC(vcc); + first = 0; + vci_dsc = eni_dev->vci+vcc->vci*16; + if (!EEPMOK(eni_vcc->rx_pos,ENI_PRV_SIZE(skb), + (readl(vci_dsc+4) & MID_VCI_READ) >> MID_VCI_READ_SHIFT, + eni_vcc->words)) { + EVENT("requeuing\n",0,0); + skb_queue_head(&eni_dev->rx_queue,skb); + break; + } + eni_vcc->rxing--; + eni_vcc->rx_pos = ENI_PRV_POS(skb) & (eni_vcc->words-1); + dma_unmap_single(&eni_dev->pci_dev->dev,ENI_PRV_PADDR(skb),skb->len, + DMA_TO_DEVICE); + if (!skb->len) dev_kfree_skb_irq(skb); + else { + EVENT("pushing (len=%ld)\n",skb->len,0); + if (vcc->qos.aal == ATM_AAL0) + *(unsigned long *) skb->data = + ntohl(*(unsigned long *) skb->data); + memset(skb->cb,0,sizeof(struct eni_skb_prv)); + vcc->push(vcc,skb); + pushed++; + } + atomic_inc(&vcc->stats->rx); + } + wake_up(&eni_dev->rx_wait); +} + + +static int open_rx_first(struct atm_vcc *vcc) +{ + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + unsigned long size; + + DPRINTK("open_rx_first\n"); + eni_dev = ENI_DEV(vcc->dev); + eni_vcc = ENI_VCC(vcc); + eni_vcc->rx = NULL; + if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0; + size = vcc->qos.rxtp.max_sdu*eni_dev->rx_mult/100; + if (size > MID_MAX_BUF_SIZE && vcc->qos.rxtp.max_sdu <= + MID_MAX_BUF_SIZE) + size = MID_MAX_BUF_SIZE; + eni_vcc->recv = eni_alloc_mem(eni_dev,&size); + DPRINTK("rx at 0x%lx\n",eni_vcc->recv); + eni_vcc->words = size >> 2; + if (!eni_vcc->recv) return -ENOBUFS; + eni_vcc->rx = vcc->qos.aal == ATM_AAL5 ? rx_aal5 : rx_aal0; + eni_vcc->descr = 0; + eni_vcc->rx_pos = 0; + eni_vcc->rxing = 0; + eni_vcc->servicing = 0; + eni_vcc->next = ENI_VCC_NOS; + return 0; +} + + +static int open_rx_second(struct atm_vcc *vcc) +{ + void __iomem *here; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + unsigned long size; + int order; + + DPRINTK("open_rx_second\n"); + eni_dev = ENI_DEV(vcc->dev); + eni_vcc = ENI_VCC(vcc); + if (!eni_vcc->rx) return 0; + /* set up VCI descriptor */ + here = eni_dev->vci+vcc->vci*16; + DPRINTK("loc 0x%x\n",(unsigned) (eni_vcc->recv-eni_dev->ram)/4); + size = eni_vcc->words >> 8; + for (order = -1; size; order++) size >>= 1; + writel(0,here+4); /* descr, read = 0 */ + writel(0,here+8); /* write, state, count = 0 */ + if (eni_dev->rx_map[vcc->vci]) + printk(KERN_CRIT DEV_LABEL "(itf %d): BUG - VCI %d already " + "in use\n",vcc->dev->number,vcc->vci); + eni_dev->rx_map[vcc->vci] = vcc; /* now it counts */ + writel(((vcc->qos.aal != ATM_AAL5 ? MID_MODE_RAW : MID_MODE_AAL5) << + MID_VCI_MODE_SHIFT) | MID_VCI_PTI_MODE | + (((eni_vcc->recv-eni_dev->ram) >> (MID_LOC_SKIP+2)) << + MID_VCI_LOCATION_SHIFT) | (order << MID_VCI_SIZE_SHIFT),here); + return 0; +} + + +static void close_rx(struct atm_vcc *vcc) +{ + DECLARE_WAITQUEUE(wait,current); + void __iomem *here; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + if (!eni_vcc->rx) return; + eni_dev = ENI_DEV(vcc->dev); + if (vcc->vpi != ATM_VPI_UNSPEC && vcc->vci != ATM_VCI_UNSPEC) { + here = eni_dev->vci+vcc->vci*16; + /* block receiver */ + writel((readl(here) & ~MID_VCI_MODE) | (MID_MODE_TRASH << + MID_VCI_MODE_SHIFT),here); + /* wait for receiver to become idle */ + udelay(27); + /* discard pending cell */ + writel(readl(here) & ~MID_VCI_IN_SERVICE,here); + /* don't accept any new ones */ + eni_dev->rx_map[vcc->vci] = NULL; + /* wait for RX queue to drain */ + DPRINTK("eni_close: waiting for RX ...\n"); + EVENT("RX closing\n",0,0); + add_wait_queue(&eni_dev->rx_wait,&wait); + set_current_state(TASK_UNINTERRUPTIBLE); + barrier(); + for (;;) { + /* transition service->rx: rxing++, servicing-- */ + if (!eni_vcc->servicing) { + barrier(); + if (!eni_vcc->rxing) break; + } + EVENT("drain PDUs (rx %ld, serv %ld)\n",eni_vcc->rxing, + eni_vcc->servicing); + printk(KERN_INFO "%d+%d RX left\n",eni_vcc->servicing, + eni_vcc->rxing); + schedule(); + set_current_state(TASK_UNINTERRUPTIBLE); + } + for (;;) { + int at_end; + u32 tmp; + + tasklet_disable(&eni_dev->task); + tmp = readl(eni_dev->vci+vcc->vci*16+4) & MID_VCI_READ; + at_end = eni_vcc->rx_pos == tmp >> MID_VCI_READ_SHIFT; + tasklet_enable(&eni_dev->task); + if (at_end) break; + EVENT("drain discard (host 0x%lx, nic 0x%lx)\n", + eni_vcc->rx_pos,tmp); + printk(KERN_INFO "draining RX: host 0x%lx, nic 0x%x\n", + eni_vcc->rx_pos,tmp); + schedule(); + set_current_state(TASK_UNINTERRUPTIBLE); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&eni_dev->rx_wait,&wait); + } + eni_free_mem(eni_dev,eni_vcc->recv,eni_vcc->words << 2); + eni_vcc->rx = NULL; +} + + +static int start_rx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + + eni_dev = ENI_DEV(dev); + eni_dev->rx_map = (struct atm_vcc **) get_zeroed_page(GFP_KERNEL); + if (!eni_dev->rx_map) { + printk(KERN_ERR DEV_LABEL "(itf %d): couldn't get free page\n", + dev->number); + free_page((unsigned long) eni_dev->free_list); + return -ENOMEM; + } + eni_dev->rx_mult = DEFAULT_RX_MULT; + eni_dev->fast = eni_dev->last_fast = NULL; + eni_dev->slow = eni_dev->last_slow = NULL; + init_waitqueue_head(&eni_dev->rx_wait); + skb_queue_head_init(&eni_dev->rx_queue); + eni_dev->serv_read = eni_in(MID_SERV_WRITE); + eni_out(0,MID_DMA_WR_RX); + return 0; +} + + +/*----------------------------------- TX ------------------------------------*/ + + +enum enq_res { enq_ok,enq_next,enq_jam }; + + +static inline void put_dma(int chan,u32 *dma,int *j,dma_addr_t paddr, + u32 size) +{ + u32 init,words; + + DPRINTK("put_dma: 0x%lx+0x%x\n",(unsigned long) paddr,size); + EVENT("put_dma: 0x%lx+0x%lx\n",(unsigned long) paddr,size); +#if 0 /* don't complain anymore */ + if (paddr & 3) + printk(KERN_ERR "put_dma: unaligned addr (0x%lx)\n",paddr); + if (size & 3) + printk(KERN_ERR "put_dma: unaligned size (0x%lx)\n",size); +#endif + if (paddr & 3) { + init = 4-(paddr & 3); + if (init > size || size < 7) init = size; + DPRINTK("put_dma: %lx DMA: %d/%d bytes\n", + (unsigned long) paddr,init,size); + dma[(*j)++] = MID_DT_BYTE | (init << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += init; + size -= init; + } + words = size >> 2; + size &= 3; + if (words && (paddr & 31)) { + init = 8-((paddr & 31) >> 2); + if (init > words) init = words; + DPRINTK("put_dma: %lx DMA: %d/%d words\n", + (unsigned long) paddr,init,words); + dma[(*j)++] = MID_DT_WORD | (init << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += init << 2; + words -= init; + } +#ifdef CONFIG_ATM_ENI_BURST_TX_16W /* may work with some PCI chipsets ... */ + if (words & ~15) { + DPRINTK("put_dma: %lx DMA: %d*16/%d words\n", + (unsigned long) paddr,words >> 4,words); + dma[(*j)++] = MID_DT_16W | ((words >> 4) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~15) << 2; + words &= 15; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_8W /* recommended */ + if (words & ~7) { + DPRINTK("put_dma: %lx DMA: %d*8/%d words\n", + (unsigned long) paddr,words >> 3,words); + dma[(*j)++] = MID_DT_8W | ((words >> 3) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~7) << 2; + words &= 7; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_4W /* probably useless if TX_8W or TX_16W */ + if (words & ~3) { + DPRINTK("put_dma: %lx DMA: %d*4/%d words\n", + (unsigned long) paddr,words >> 2,words); + dma[(*j)++] = MID_DT_4W | ((words >> 2) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~3) << 2; + words &= 3; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_2W /* probably useless if TX_4W, TX_8W, ... */ + if (words & ~1) { + DPRINTK("put_dma: %lx DMA: %d*2/%d words\n", + (unsigned long) paddr,words >> 1,words); + dma[(*j)++] = MID_DT_2W | ((words >> 1) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~1) << 2; + words &= 1; + } +#endif + if (words) { + DPRINTK("put_dma: %lx DMA: %d words\n",(unsigned long) paddr, + words); + dma[(*j)++] = MID_DT_WORD | (words << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += words << 2; + } + if (size) { + DPRINTK("put_dma: %lx DMA: %d bytes\n",(unsigned long) paddr, + size); + dma[(*j)++] = MID_DT_BYTE | (size << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + } +} + + +static enum enq_res do_tx(struct sk_buff *skb) +{ + struct atm_vcc *vcc; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + struct eni_tx *tx; + dma_addr_t paddr; + u32 dma_rd,dma_wr; + u32 size; /* in words */ + int aal5,dma_size,i,j; + unsigned char skb_data3; + + DPRINTK(">do_tx\n"); + NULLCHECK(skb); + EVENT("do_tx: skb=0x%lx, %ld bytes\n",(unsigned long) skb,skb->len); + vcc = ATM_SKB(skb)->vcc; + NULLCHECK(vcc); + eni_dev = ENI_DEV(vcc->dev); + NULLCHECK(eni_dev); + eni_vcc = ENI_VCC(vcc); + tx = eni_vcc->tx; + NULLCHECK(tx); +#if 0 /* Enable this for testing with the "align" program */ + { + unsigned int hack = *((char *) skb->data)-'0'; + + if (hack < 8) { + skb->data += hack; + skb->len -= hack; + } + } +#endif +#if 0 /* should work now */ + if ((unsigned long) skb->data & 3) + printk(KERN_ERR DEV_LABEL "(itf %d): VCI %d has mis-aligned " + "TX data\n",vcc->dev->number,vcc->vci); +#endif + /* + * Potential future IP speedup: make hard_header big enough to put + * segmentation descriptor directly into PDU. Saves: 4 slave writes, + * 1 DMA xfer & 2 DMA'ed bytes (protocol layering is for wimps :-) + */ + + aal5 = vcc->qos.aal == ATM_AAL5; + /* check space in buffer */ + if (!aal5) + size = (ATM_CELL_PAYLOAD >> 2)+TX_DESCR_SIZE; + /* cell without HEC plus segmentation header (includes + four-byte cell header) */ + else { + size = skb->len+4*AAL5_TRAILER+ATM_CELL_PAYLOAD-1; + /* add AAL5 trailer */ + size = ((size-(size % ATM_CELL_PAYLOAD)) >> 2)+TX_DESCR_SIZE; + /* add segmentation header */ + } + /* + * Can I move tx_pos by size bytes without getting closer than TX_GAP + * to the read pointer ? TX_GAP means to leave some space for what + * the manual calls "too close". + */ + if (!NEPMOK(tx->tx_pos,size+TX_GAP, + eni_in(MID_TX_RDPTR(tx->index)),tx->words)) { + DPRINTK(DEV_LABEL "(itf %d): TX full (size %d)\n", + vcc->dev->number,size); + return enq_next; + } + /* check DMA */ + dma_wr = eni_in(MID_DMA_WR_TX); + dma_rd = eni_in(MID_DMA_RD_TX); + dma_size = 3; /* JK for descriptor and final fill, plus final size + mis-alignment fix */ +DPRINTK("iovcnt = %d\n",skb_shinfo(skb)->nr_frags); + if (!skb_shinfo(skb)->nr_frags) dma_size += 5; + else dma_size += 5*(skb_shinfo(skb)->nr_frags+1); + if (dma_size > TX_DMA_BUF) { + printk(KERN_CRIT DEV_LABEL "(itf %d): needs %d DMA entries " + "(got only %d)\n",vcc->dev->number,dma_size,TX_DMA_BUF); + } + DPRINTK("dma_wr is %d, tx_pos is %ld\n",dma_wr,tx->tx_pos); + if (dma_wr != dma_rd && ((dma_rd+NR_DMA_TX-dma_wr) & (NR_DMA_TX-1)) < + dma_size) { + printk(KERN_WARNING DEV_LABEL "(itf %d): TX DMA full\n", + vcc->dev->number); + return enq_jam; + } + skb_data3 = skb->data[3]; + paddr = dma_map_single(&eni_dev->pci_dev->dev,skb->data,skb->len, + DMA_TO_DEVICE); + if (dma_mapping_error(&eni_dev->pci_dev->dev, paddr)) + return enq_next; + ENI_PRV_PADDR(skb) = paddr; + /* prepare DMA queue entries */ + j = 0; + eni_dev->dma[j++] = (((tx->tx_pos+TX_DESCR_SIZE) & (tx->words-1)) << + MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) | + MID_DT_JK; + j++; + if (!skb_shinfo(skb)->nr_frags) + if (aal5) put_dma(tx->index,eni_dev->dma,&j,paddr,skb->len); + else put_dma(tx->index,eni_dev->dma,&j,paddr+4,skb->len-4); + else { +DPRINTK("doing direct send\n"); /* @@@ well, this doesn't work anyway */ + for (i = -1; i < skb_shinfo(skb)->nr_frags; i++) + if (i == -1) + put_dma(tx->index,eni_dev->dma,&j,(unsigned long) + skb->data, + skb_headlen(skb)); + else + put_dma(tx->index,eni_dev->dma,&j,(unsigned long) + skb_frag_page(&skb_shinfo(skb)->frags[i]) + + skb_frag_off(&skb_shinfo(skb)->frags[i]), + skb_frag_size(&skb_shinfo(skb)->frags[i])); + } + if (skb->len & 3) { + put_dma(tx->index, eni_dev->dma, &j, eni_dev->zero.dma, + 4 - (skb->len & 3)); + } + /* JK for AAL5 trailer - AAL0 doesn't need it, but who cares ... */ + eni_dev->dma[j++] = (((tx->tx_pos+size) & (tx->words-1)) << + MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) | + MID_DMA_END | MID_DT_JK; + j++; + DPRINTK("DMA at end: %d\n",j); + /* store frame */ + writel((MID_SEG_TX_ID << MID_SEG_ID_SHIFT) | + (aal5 ? MID_SEG_AAL5 : 0) | (tx->prescaler << MID_SEG_PR_SHIFT) | + (tx->resolution << MID_SEG_RATE_SHIFT) | + (size/(ATM_CELL_PAYLOAD/4)),tx->send+tx->tx_pos*4); +/*printk("dsc = 0x%08lx\n",(unsigned long) readl(tx->send+tx->tx_pos*4));*/ + writel((vcc->vci << MID_SEG_VCI_SHIFT) | + (aal5 ? 0 : (skb_data3 & 0xf)) | + (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? MID_SEG_CLP : 0), + tx->send+((tx->tx_pos+1) & (tx->words-1))*4); + DPRINTK("size: %d, len:%d\n",size,skb->len); + if (aal5) + writel(skb->len,tx->send+ + ((tx->tx_pos+size-AAL5_TRAILER) & (tx->words-1))*4); + j = j >> 1; + for (i = 0; i < j; i++) { + writel(eni_dev->dma[i*2],eni_dev->tx_dma+dma_wr*8); + writel(eni_dev->dma[i*2+1],eni_dev->tx_dma+dma_wr*8+4); + dma_wr = (dma_wr+1) & (NR_DMA_TX-1); + } + ENI_PRV_POS(skb) = tx->tx_pos; + ENI_PRV_SIZE(skb) = size; + ENI_VCC(vcc)->txing += size; + tx->tx_pos = (tx->tx_pos+size) & (tx->words-1); + DPRINTK("dma_wr set to %d, tx_pos is now %ld\n",dma_wr,tx->tx_pos); + eni_out(dma_wr,MID_DMA_WR_TX); + skb_queue_tail(&eni_dev->tx_queue,skb); + queued++; + return enq_ok; +} + + +static void poll_tx(struct atm_dev *dev) +{ + struct eni_tx *tx; + struct sk_buff *skb; + enum enq_res res; + int i; + + DPRINTK(">poll_tx\n"); + for (i = NR_CHAN-1; i >= 0; i--) { + tx = &ENI_DEV(dev)->tx[i]; + if (tx->send) + while ((skb = skb_dequeue(&tx->backlog))) { + res = do_tx(skb); + if (res == enq_ok) continue; + DPRINTK("re-queuing TX PDU\n"); + skb_queue_head(&tx->backlog,skb); + requeued++; + if (res == enq_jam) return; + break; + } + } +} + + +static void dequeue_tx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct atm_vcc *vcc; + struct sk_buff *skb; + struct eni_tx *tx; + + NULLCHECK(dev); + eni_dev = ENI_DEV(dev); + NULLCHECK(eni_dev); + while ((skb = skb_dequeue(&eni_dev->tx_queue))) { + vcc = ATM_SKB(skb)->vcc; + NULLCHECK(vcc); + tx = ENI_VCC(vcc)->tx; + NULLCHECK(ENI_VCC(vcc)->tx); + DPRINTK("dequeue_tx: next 0x%lx curr 0x%x\n",ENI_PRV_POS(skb), + (unsigned) eni_in(MID_TX_DESCRSTART(tx->index))); + if (ENI_VCC(vcc)->txing < tx->words && ENI_PRV_POS(skb) == + eni_in(MID_TX_DESCRSTART(tx->index))) { + skb_queue_head(&eni_dev->tx_queue,skb); + break; + } + ENI_VCC(vcc)->txing -= ENI_PRV_SIZE(skb); + dma_unmap_single(&eni_dev->pci_dev->dev,ENI_PRV_PADDR(skb),skb->len, + DMA_TO_DEVICE); + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb_irq(skb); + atomic_inc(&vcc->stats->tx); + wake_up(&eni_dev->tx_wait); + dma_complete++; + } +} + + +static struct eni_tx *alloc_tx(struct eni_dev *eni_dev,int ubr) +{ + int i; + + for (i = !ubr; i < NR_CHAN; i++) + if (!eni_dev->tx[i].send) return eni_dev->tx+i; + return NULL; +} + + +static int comp_tx(struct eni_dev *eni_dev,int *pcr,int reserved,int *pre, + int *res,int unlimited) +{ + static const int pre_div[] = { 4,16,128,2048 }; + /* 2^(((x+2)^2-(x+2))/2+1) */ + + if (unlimited) *pre = *res = 0; + else { + if (*pcr > 0) { + int div; + + for (*pre = 0; *pre < 3; (*pre)++) + if (TS_CLOCK/pre_div[*pre]/64 <= *pcr) break; + div = pre_div[*pre]**pcr; + DPRINTK("min div %d\n",div); + *res = TS_CLOCK/div-1; + } + else { + int div; + + if (!*pcr) *pcr = eni_dev->tx_bw+reserved; + for (*pre = 3; *pre >= 0; (*pre)--) + if (TS_CLOCK/pre_div[*pre]/64 > -*pcr) break; + if (*pre < 3) (*pre)++; /* else fail later */ + div = pre_div[*pre]*-*pcr; + DPRINTK("max div %d\n",div); + *res = DIV_ROUND_UP(TS_CLOCK, div)-1; + } + if (*res < 0) *res = 0; + if (*res > MID_SEG_MAX_RATE) *res = MID_SEG_MAX_RATE; + } + *pcr = TS_CLOCK/pre_div[*pre]/(*res+1); + DPRINTK("out pcr: %d (%d:%d)\n",*pcr,*pre,*res); + return 0; +} + + +static int reserve_or_set_tx(struct atm_vcc *vcc,struct atm_trafprm *txtp, + int set_rsv,int set_shp) +{ + struct eni_dev *eni_dev = ENI_DEV(vcc->dev); + struct eni_vcc *eni_vcc = ENI_VCC(vcc); + struct eni_tx *tx; + unsigned long size; + void __iomem *mem; + int rate,ubr,unlimited,new_tx; + int pre,res,order; + int error; + + rate = atm_pcr_goal(txtp); + ubr = txtp->traffic_class == ATM_UBR; + unlimited = ubr && (!rate || rate <= -ATM_OC3_PCR || + rate >= ATM_OC3_PCR); + if (!unlimited) { + size = txtp->max_sdu*eni_dev->tx_mult/100; + if (size > MID_MAX_BUF_SIZE && txtp->max_sdu <= + MID_MAX_BUF_SIZE) + size = MID_MAX_BUF_SIZE; + } + else { + if (eni_dev->ubr) { + eni_vcc->tx = eni_dev->ubr; + txtp->pcr = ATM_OC3_PCR; + return 0; + } + size = UBR_BUFFER; + } + new_tx = !eni_vcc->tx; + mem = NULL; /* for gcc */ + if (!new_tx) tx = eni_vcc->tx; + else { + mem = eni_alloc_mem(eni_dev,&size); + if (!mem) return -ENOBUFS; + tx = alloc_tx(eni_dev,unlimited); + if (!tx) { + eni_free_mem(eni_dev,mem,size); + return -EBUSY; + } + DPRINTK("got chan %d\n",tx->index); + tx->reserved = tx->shaping = 0; + tx->send = mem; + tx->words = size >> 2; + skb_queue_head_init(&tx->backlog); + for (order = 0; size > (1 << (order+10)); order++); + eni_out((order << MID_SIZE_SHIFT) | + ((tx->send-eni_dev->ram) >> (MID_LOC_SKIP+2)), + MID_TX_PLACE(tx->index)); + tx->tx_pos = eni_in(MID_TX_DESCRSTART(tx->index)) & + MID_DESCR_START; + } + error = comp_tx(eni_dev,&rate,tx->reserved,&pre,&res,unlimited); + if (!error && txtp->min_pcr > rate) error = -EINVAL; + if (!error && txtp->max_pcr && txtp->max_pcr != ATM_MAX_PCR && + txtp->max_pcr < rate) error = -EINVAL; + if (!error && !ubr && rate > eni_dev->tx_bw+tx->reserved) + error = -EINVAL; + if (!error && set_rsv && !set_shp && rate < tx->shaping) + error = -EINVAL; + if (!error && !set_rsv && rate > tx->reserved && !ubr) + error = -EINVAL; + if (error) { + if (new_tx) { + tx->send = NULL; + eni_free_mem(eni_dev,mem,size); + } + return error; + } + txtp->pcr = rate; + if (set_rsv && !ubr) { + eni_dev->tx_bw += tx->reserved; + tx->reserved = rate; + eni_dev->tx_bw -= rate; + } + if (set_shp || (unlimited && new_tx)) { + if (unlimited && new_tx) eni_dev->ubr = tx; + tx->prescaler = pre; + tx->resolution = res; + tx->shaping = rate; + } + if (set_shp) eni_vcc->tx = tx; + DPRINTK("rsv %d shp %d\n",tx->reserved,tx->shaping); + return 0; +} + + +static int open_tx_first(struct atm_vcc *vcc) +{ + ENI_VCC(vcc)->tx = NULL; + if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0; + ENI_VCC(vcc)->txing = 0; + return reserve_or_set_tx(vcc,&vcc->qos.txtp,1,1); +} + + +static int open_tx_second(struct atm_vcc *vcc) +{ + return 0; /* nothing to do */ +} + + +static void close_tx(struct atm_vcc *vcc) +{ + DECLARE_WAITQUEUE(wait,current); + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + if (!eni_vcc->tx) return; + eni_dev = ENI_DEV(vcc->dev); + /* wait for TX queue to drain */ + DPRINTK("eni_close: waiting for TX ...\n"); + add_wait_queue(&eni_dev->tx_wait,&wait); + set_current_state(TASK_UNINTERRUPTIBLE); + for (;;) { + int txing; + + tasklet_disable(&eni_dev->task); + txing = skb_peek(&eni_vcc->tx->backlog) || eni_vcc->txing; + tasklet_enable(&eni_dev->task); + if (!txing) break; + DPRINTK("%d TX left\n",eni_vcc->txing); + schedule(); + set_current_state(TASK_UNINTERRUPTIBLE); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&eni_dev->tx_wait,&wait); + if (eni_vcc->tx != eni_dev->ubr) { + /* + * Looping a few times in here is probably far cheaper than + * keeping track of TX completions all the time, so let's poll + * a bit ... + */ + while (eni_in(MID_TX_RDPTR(eni_vcc->tx->index)) != + eni_in(MID_TX_DESCRSTART(eni_vcc->tx->index))) + schedule(); + eni_free_mem(eni_dev,eni_vcc->tx->send,eni_vcc->tx->words << 2); + eni_vcc->tx->send = NULL; + eni_dev->tx_bw += eni_vcc->tx->reserved; + } + eni_vcc->tx = NULL; +} + + +static int start_tx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + int i; + + eni_dev = ENI_DEV(dev); + eni_dev->lost = 0; + eni_dev->tx_bw = ATM_OC3_PCR; + eni_dev->tx_mult = DEFAULT_TX_MULT; + init_waitqueue_head(&eni_dev->tx_wait); + eni_dev->ubr = NULL; + skb_queue_head_init(&eni_dev->tx_queue); + eni_out(0,MID_DMA_WR_TX); + for (i = 0; i < NR_CHAN; i++) { + eni_dev->tx[i].send = NULL; + eni_dev->tx[i].index = i; + } + return 0; +} + + +/*--------------------------------- common ----------------------------------*/ + + +#if 0 /* may become useful again when tuning things */ + +static void foo(void) +{ +printk(KERN_INFO + "tx_complete=%d,dma_complete=%d,queued=%d,requeued=%d,sub=%d,\n" + "backlogged=%d,rx_enqueued=%d,rx_dequeued=%d,putting=%d,pushed=%d\n", + tx_complete,dma_complete,queued,requeued,submitted,backlogged, + rx_enqueued,rx_dequeued,putting,pushed); +if (eni_boards) printk(KERN_INFO "loss: %ld\n",ENI_DEV(eni_boards)->lost); +} + +#endif + + +static void bug_int(struct atm_dev *dev,unsigned long reason) +{ + DPRINTK(">bug_int\n"); + if (reason & MID_DMA_ERR_ACK) + printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - DMA " + "error\n",dev->number); + if (reason & MID_TX_IDENT_MISM) + printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - ident " + "mismatch\n",dev->number); + if (reason & MID_TX_DMA_OVFL) + printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - DMA " + "overflow\n",dev->number); + EVENT("---dump ends here---\n",0,0); + printk(KERN_NOTICE "---recent events---\n"); + event_dump(); +} + + +static irqreturn_t eni_int(int irq,void *dev_id) +{ + struct atm_dev *dev; + struct eni_dev *eni_dev; + u32 reason; + + DPRINTK(">eni_int\n"); + dev = dev_id; + eni_dev = ENI_DEV(dev); + reason = eni_in(MID_ISA); + DPRINTK(DEV_LABEL ": int 0x%lx\n",(unsigned long) reason); + /* + * Must handle these two right now, because reading ISA doesn't clear + * them, so they re-occur and we never make it to the tasklet. Since + * they're rare, we don't mind the occasional invocation of eni_tasklet + * with eni_dev->events == 0. + */ + if (reason & MID_STAT_OVFL) { + EVENT("stat overflow\n",0,0); + eni_dev->lost += eni_in(MID_STAT) & MID_OVFL_TRASH; + } + if (reason & MID_SUNI_INT) { + EVENT("SUNI int\n",0,0); + dev->phy->interrupt(dev); +#if 0 + foo(); +#endif + } + spin_lock(&eni_dev->lock); + eni_dev->events |= reason; + spin_unlock(&eni_dev->lock); + tasklet_schedule(&eni_dev->task); + return IRQ_HANDLED; +} + + +static void eni_tasklet(unsigned long data) +{ + struct atm_dev *dev = (struct atm_dev *) data; + struct eni_dev *eni_dev = ENI_DEV(dev); + unsigned long flags; + u32 events; + + DPRINTK("eni_tasklet (dev %p)\n",dev); + spin_lock_irqsave(&eni_dev->lock,flags); + events = xchg(&eni_dev->events,0); + spin_unlock_irqrestore(&eni_dev->lock,flags); + if (events & MID_RX_DMA_COMPLETE) { + EVENT("INT: RX DMA complete, starting dequeue_rx\n",0,0); + dequeue_rx(dev); + EVENT("dequeue_rx done, starting poll_rx\n",0,0); + poll_rx(dev); + EVENT("poll_rx done\n",0,0); + /* poll_tx ? */ + } + if (events & MID_SERVICE) { + EVENT("INT: service, starting get_service\n",0,0); + get_service(dev); + EVENT("get_service done, starting poll_rx\n",0,0); + poll_rx(dev); + EVENT("poll_rx done\n",0,0); + } + if (events & MID_TX_DMA_COMPLETE) { + EVENT("INT: TX DMA COMPLETE\n",0,0); + dequeue_tx(dev); + } + if (events & MID_TX_COMPLETE) { + EVENT("INT: TX COMPLETE\n",0,0); + tx_complete++; + wake_up(&eni_dev->tx_wait); + /* poll_rx ? */ + } + if (events & (MID_DMA_ERR_ACK | MID_TX_IDENT_MISM | MID_TX_DMA_OVFL)) { + EVENT("bug interrupt\n",0,0); + bug_int(dev,events); + } + poll_tx(dev); +} + + +/*--------------------------------- entries ---------------------------------*/ + + +static char * const media_name[] = { + "MMF", "SMF", "MMF", "03?", /* 0- 3 */ + "UTP", "05?", "06?", "07?", /* 4- 7 */ + "TAXI","09?", "10?", "11?", /* 8-11 */ + "12?", "13?", "14?", "15?", /* 12-15 */ + "MMF", "SMF", "18?", "19?", /* 16-19 */ + "UTP", "21?", "22?", "23?", /* 20-23 */ + "24?", "25?", "26?", "27?", /* 24-27 */ + "28?", "29?", "30?", "31?" /* 28-31 */ +}; + + +#define SET_SEPROM \ + ({ if (!error && !pci_error) { \ + pci_error = pci_write_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,tonga); \ + udelay(10); /* 10 usecs */ \ + } }) +#define GET_SEPROM \ + ({ if (!error && !pci_error) { \ + pci_error = pci_read_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,&tonga); \ + udelay(10); /* 10 usecs */ \ + } }) + + +static int get_esi_asic(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + unsigned char tonga; + int error,failed,pci_error; + int address,i,j; + + eni_dev = ENI_DEV(dev); + error = pci_error = 0; + tonga = SEPROM_MAGIC | SEPROM_DATA | SEPROM_CLK; + SET_SEPROM; + for (i = 0; i < ESI_LEN && !error && !pci_error; i++) { + /* start operation */ + tonga |= SEPROM_DATA; + SET_SEPROM; + tonga |= SEPROM_CLK; + SET_SEPROM; + tonga &= ~SEPROM_DATA; + SET_SEPROM; + tonga &= ~SEPROM_CLK; + SET_SEPROM; + /* send address */ + address = ((i+SEPROM_ESI_BASE) << 1)+1; + for (j = 7; j >= 0; j--) { + tonga = (address >> j) & 1 ? tonga | SEPROM_DATA : + tonga & ~SEPROM_DATA; + SET_SEPROM; + tonga |= SEPROM_CLK; + SET_SEPROM; + tonga &= ~SEPROM_CLK; + SET_SEPROM; + } + /* get ack */ + tonga |= SEPROM_DATA; + SET_SEPROM; + tonga |= SEPROM_CLK; + SET_SEPROM; + GET_SEPROM; + failed = tonga & SEPROM_DATA; + tonga &= ~SEPROM_CLK; + SET_SEPROM; + tonga |= SEPROM_DATA; + SET_SEPROM; + if (failed) error = -EIO; + else { + dev->esi[i] = 0; + for (j = 7; j >= 0; j--) { + dev->esi[i] <<= 1; + tonga |= SEPROM_DATA; + SET_SEPROM; + tonga |= SEPROM_CLK; + SET_SEPROM; + GET_SEPROM; + if (tonga & SEPROM_DATA) dev->esi[i] |= 1; + tonga &= ~SEPROM_CLK; + SET_SEPROM; + tonga |= SEPROM_DATA; + SET_SEPROM; + } + /* get ack */ + tonga |= SEPROM_DATA; + SET_SEPROM; + tonga |= SEPROM_CLK; + SET_SEPROM; + GET_SEPROM; + if (!(tonga & SEPROM_DATA)) error = -EIO; + tonga &= ~SEPROM_CLK; + SET_SEPROM; + tonga |= SEPROM_DATA; + SET_SEPROM; + } + /* stop operation */ + tonga &= ~SEPROM_DATA; + SET_SEPROM; + tonga |= SEPROM_CLK; + SET_SEPROM; + tonga |= SEPROM_DATA; + SET_SEPROM; + } + if (pci_error) { + printk(KERN_ERR DEV_LABEL "(itf %d): error reading ESI " + "(0x%02x)\n",dev->number,pci_error); + error = -EIO; + } + return error; +} + + +#undef SET_SEPROM +#undef GET_SEPROM + + +static int get_esi_fpga(struct atm_dev *dev, void __iomem *base) +{ + void __iomem *mac_base; + int i; + + mac_base = base+EPROM_SIZE-sizeof(struct midway_eprom); + for (i = 0; i < ESI_LEN; i++) dev->esi[i] = readb(mac_base+(i^3)); + return 0; +} + + +static int eni_do_init(struct atm_dev *dev) +{ + struct midway_eprom __iomem *eprom; + struct eni_dev *eni_dev; + struct pci_dev *pci_dev; + unsigned long real_base; + void __iomem *base; + int error,i,last; + + DPRINTK(">eni_init\n"); + dev->ci_range.vpi_bits = 0; + dev->ci_range.vci_bits = NR_VCI_LD; + dev->link_rate = ATM_OC3_PCR; + eni_dev = ENI_DEV(dev); + pci_dev = eni_dev->pci_dev; + real_base = pci_resource_start(pci_dev, 0); + eni_dev->irq = pci_dev->irq; + if ((error = pci_write_config_word(pci_dev,PCI_COMMAND, + PCI_COMMAND_MEMORY | + (eni_dev->asic ? PCI_COMMAND_PARITY | PCI_COMMAND_SERR : 0)))) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory " + "(0x%02x)\n",dev->number,error); + return -EIO; + } + printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d,base=0x%lx,irq=%d,", + dev->number,pci_dev->revision,real_base,eni_dev->irq); + if (!(base = ioremap(real_base,MAP_MAX_SIZE))) { + printk("\n"); + printk(KERN_ERR DEV_LABEL "(itf %d): can't set up page " + "mapping\n",dev->number); + return -ENOMEM; + } + eni_dev->ioaddr = base; + eni_dev->base_diff = real_base - (unsigned long) base; + /* id may not be present in ASIC Tonga boards - check this @@@ */ + if (!eni_dev->asic) { + eprom = (base+EPROM_SIZE-sizeof(struct midway_eprom)); + if (readl(&eprom->magic) != ENI155_MAGIC) { + printk("\n"); + printk(KERN_ERR DEV_LABEL + "(itf %d): bad magic - expected 0x%x, got 0x%x\n", + dev->number, ENI155_MAGIC, + (unsigned)readl(&eprom->magic)); + error = -EINVAL; + goto unmap; + } + } + eni_dev->phy = base+PHY_BASE; + eni_dev->reg = base+REG_BASE; + eni_dev->ram = base+RAM_BASE; + last = MAP_MAX_SIZE-RAM_BASE; + for (i = last-RAM_INCREMENT; i >= 0; i -= RAM_INCREMENT) { + writel(0x55555555,eni_dev->ram+i); + if (readl(eni_dev->ram+i) != 0x55555555) last = i; + else { + writel(0xAAAAAAAA,eni_dev->ram+i); + if (readl(eni_dev->ram+i) != 0xAAAAAAAA) last = i; + else writel(i,eni_dev->ram+i); + } + } + for (i = 0; i < last; i += RAM_INCREMENT) + if (readl(eni_dev->ram+i) != i) break; + eni_dev->mem = i; + memset_io(eni_dev->ram,0,eni_dev->mem); + /* TODO: should shrink allocation now */ + printk("mem=%dkB (",eni_dev->mem >> 10); + /* TODO: check for non-SUNI, check for TAXI ? */ + if (!(eni_in(MID_RES_ID_MCON) & 0x200) != !eni_dev->asic) { + printk(")\n"); + printk(KERN_ERR DEV_LABEL "(itf %d): ERROR - wrong id 0x%x\n", + dev->number,(unsigned) eni_in(MID_RES_ID_MCON)); + error = -EINVAL; + goto unmap; + } + error = eni_dev->asic ? get_esi_asic(dev) : get_esi_fpga(dev,base); + if (error) + goto unmap; + for (i = 0; i < ESI_LEN; i++) + printk("%s%02X",i ? "-" : "",dev->esi[i]); + printk(")\n"); + printk(KERN_NOTICE DEV_LABEL "(itf %d): %s,%s\n",dev->number, + eni_in(MID_RES_ID_MCON) & 0x200 ? "ASIC" : "FPGA", + media_name[eni_in(MID_RES_ID_MCON) & DAUGHTER_ID]); + + error = suni_init(dev); + if (error) + goto unmap; +out: + return error; +unmap: + iounmap(base); + goto out; +} + +static void eni_do_release(struct atm_dev *dev) +{ + struct eni_dev *ed = ENI_DEV(dev); + + dev->phy->stop(dev); + dev->phy = NULL; + iounmap(ed->ioaddr); +} + +static int eni_start(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + + void __iomem *buf; + unsigned long buffer_mem; + int error; + + DPRINTK(">eni_start\n"); + eni_dev = ENI_DEV(dev); + if (request_irq(eni_dev->irq,&eni_int,IRQF_SHARED,DEV_LABEL,dev)) { + printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n", + dev->number,eni_dev->irq); + error = -EAGAIN; + goto out; + } + pci_set_master(eni_dev->pci_dev); + if ((error = pci_write_config_word(eni_dev->pci_dev,PCI_COMMAND, + PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | + (eni_dev->asic ? PCI_COMMAND_PARITY | PCI_COMMAND_SERR : 0)))) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory+" + "master (0x%02x)\n",dev->number,error); + goto free_irq; + } + if ((error = pci_write_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL, + END_SWAP_DMA))) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't set endian swap " + "(0x%02x)\n",dev->number,error); + goto free_irq; + } + /* determine addresses of internal tables */ + eni_dev->vci = eni_dev->ram; + eni_dev->rx_dma = eni_dev->ram+NR_VCI*16; + eni_dev->tx_dma = eni_dev->rx_dma+NR_DMA_RX*8; + eni_dev->service = eni_dev->tx_dma+NR_DMA_TX*8; + buf = eni_dev->service+NR_SERVICE*4; + DPRINTK("vci 0x%lx,rx 0x%lx, tx 0x%lx,srv 0x%lx,buf 0x%lx\n", + eni_dev->vci,eni_dev->rx_dma,eni_dev->tx_dma, + eni_dev->service,buf); + spin_lock_init(&eni_dev->lock); + tasklet_init(&eni_dev->task,eni_tasklet,(unsigned long) dev); + eni_dev->events = 0; + /* initialize memory management */ + buffer_mem = eni_dev->mem - (buf - eni_dev->ram); + eni_dev->free_list_size = buffer_mem/MID_MIN_BUF_SIZE/2; + eni_dev->free_list = kmalloc_array(eni_dev->free_list_size + 1, + sizeof(*eni_dev->free_list), + GFP_KERNEL); + if (!eni_dev->free_list) { + printk(KERN_ERR DEV_LABEL "(itf %d): couldn't get free page\n", + dev->number); + error = -ENOMEM; + goto free_irq; + } + eni_dev->free_len = 0; + eni_put_free(eni_dev,buf,buffer_mem); + memset_io(eni_dev->vci,0,16*NR_VCI); /* clear VCI table */ + /* + * byte_addr free (k) + * 0x00000000 512 VCI table + * 0x00004000 496 RX DMA + * 0x00005000 492 TX DMA + * 0x00006000 488 service list + * 0x00007000 484 buffers + * 0x00080000 0 end (512kB) + */ + eni_out(0xffffffff,MID_IE); + error = start_tx(dev); + if (error) goto free_list; + error = start_rx(dev); + if (error) goto free_list; + error = dev->phy->start(dev); + if (error) goto free_list; + eni_out(eni_in(MID_MC_S) | (1 << MID_INT_SEL_SHIFT) | + MID_TX_LOCK_MODE | MID_DMA_ENABLE | MID_TX_ENABLE | MID_RX_ENABLE, + MID_MC_S); + /* Tonga uses SBus INTReq1 */ + (void) eni_in(MID_ISA); /* clear Midway interrupts */ + return 0; + +free_list: + kfree(eni_dev->free_list); + +free_irq: + free_irq(eni_dev->irq, dev); + +out: + return error; +} + + +static void eni_close(struct atm_vcc *vcc) +{ + DPRINTK(">eni_close\n"); + if (!ENI_VCC(vcc)) return; + clear_bit(ATM_VF_READY,&vcc->flags); + close_rx(vcc); + close_tx(vcc); + DPRINTK("eni_close: done waiting\n"); + /* deallocate memory */ + kfree(ENI_VCC(vcc)); + vcc->dev_data = NULL; + clear_bit(ATM_VF_ADDR,&vcc->flags); + /*foo();*/ +} + + +static int eni_open(struct atm_vcc *vcc) +{ + struct eni_vcc *eni_vcc; + int error; + short vpi = vcc->vpi; + int vci = vcc->vci; + + DPRINTK(">eni_open\n"); + EVENT("eni_open\n",0,0); + if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) + vcc->dev_data = NULL; + if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC) + set_bit(ATM_VF_ADDR,&vcc->flags); + if (vcc->qos.aal != ATM_AAL0 && vcc->qos.aal != ATM_AAL5) + return -EINVAL; + DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n",vcc->dev->number,vcc->vpi, + vcc->vci); + if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) { + eni_vcc = kmalloc(sizeof(struct eni_vcc),GFP_KERNEL); + if (!eni_vcc) return -ENOMEM; + vcc->dev_data = eni_vcc; + eni_vcc->tx = NULL; /* for eni_close after open_rx */ + if ((error = open_rx_first(vcc))) { + eni_close(vcc); + return error; + } + if ((error = open_tx_first(vcc))) { + eni_close(vcc); + return error; + } + } + if (vci == ATM_VPI_UNSPEC || vpi == ATM_VCI_UNSPEC) return 0; + if ((error = open_rx_second(vcc))) { + eni_close(vcc); + return error; + } + if ((error = open_tx_second(vcc))) { + eni_close(vcc); + return error; + } + set_bit(ATM_VF_READY,&vcc->flags); + /* should power down SUNI while !ref_count @@@ */ + return 0; +} + + +static int eni_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flgs) +{ + struct eni_dev *eni_dev = ENI_DEV(vcc->dev); + struct eni_tx *tx = ENI_VCC(vcc)->tx; + struct sk_buff *skb; + int error,rate,rsv,shp; + + if (qos->txtp.traffic_class == ATM_NONE) return 0; + if (tx == eni_dev->ubr) return -EBADFD; + rate = atm_pcr_goal(&qos->txtp); + if (rate < 0) rate = -rate; + rsv = shp = 0; + if ((flgs & ATM_MF_DEC_RSV) && rate && rate < tx->reserved) rsv = 1; + if ((flgs & ATM_MF_INC_RSV) && (!rate || rate > tx->reserved)) rsv = 1; + if ((flgs & ATM_MF_DEC_SHP) && rate && rate < tx->shaping) shp = 1; + if ((flgs & ATM_MF_INC_SHP) && (!rate || rate > tx->shaping)) shp = 1; + if (!rsv && !shp) return 0; + error = reserve_or_set_tx(vcc,&qos->txtp,rsv,shp); + if (error) return error; + if (shp && !(flgs & ATM_MF_IMMED)) return 0; + /* + * Walk through the send buffer and patch the rate information in all + * segmentation buffer descriptors of this VCC. + */ + tasklet_disable(&eni_dev->task); + skb_queue_walk(&eni_dev->tx_queue, skb) { + void __iomem *dsc; + + if (ATM_SKB(skb)->vcc != vcc) continue; + dsc = tx->send+ENI_PRV_POS(skb)*4; + writel((readl(dsc) & ~(MID_SEG_RATE | MID_SEG_PR)) | + (tx->prescaler << MID_SEG_PR_SHIFT) | + (tx->resolution << MID_SEG_RATE_SHIFT), dsc); + } + tasklet_enable(&eni_dev->task); + return 0; +} + + +static int eni_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + struct eni_dev *eni_dev = ENI_DEV(dev); + + if (cmd == ENI_MEMDUMP) { + if (!capable(CAP_NET_ADMIN)) return -EPERM; + printk(KERN_WARNING "Please use /proc/atm/" DEV_LABEL ":%d " + "instead of obsolete ioctl ENI_MEMDUMP\n",dev->number); + dump(dev); + return 0; + } + if (cmd == ENI_SETMULT) { + struct eni_multipliers mult; + + if (!capable(CAP_NET_ADMIN)) return -EPERM; + if (copy_from_user(&mult, arg, + sizeof(struct eni_multipliers))) + return -EFAULT; + if ((mult.tx && mult.tx <= 100) || (mult.rx &&mult.rx <= 100) || + mult.tx > 65536 || mult.rx > 65536) + return -EINVAL; + if (mult.tx) eni_dev->tx_mult = mult.tx; + if (mult.rx) eni_dev->rx_mult = mult.rx; + return 0; + } + if (cmd == ATM_SETCIRANGE) { + struct atm_cirange ci; + + if (copy_from_user(&ci, arg,sizeof(struct atm_cirange))) + return -EFAULT; + if ((ci.vpi_bits == 0 || ci.vpi_bits == ATM_CI_MAX) && + (ci.vci_bits == NR_VCI_LD || ci.vpi_bits == ATM_CI_MAX)) + return 0; + return -EINVAL; + } + if (!dev->phy->ioctl) return -ENOIOCTLCMD; + return dev->phy->ioctl(dev,cmd,arg); +} + +static int eni_send(struct atm_vcc *vcc,struct sk_buff *skb) +{ + enum enq_res res; + + DPRINTK(">eni_send\n"); + if (!ENI_VCC(vcc)->tx) { + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + return -EINVAL; + } + if (!skb) { + printk(KERN_CRIT "!skb in eni_send ?\n"); + if (vcc->pop) vcc->pop(vcc,skb); + return -EINVAL; + } + if (vcc->qos.aal == ATM_AAL0) { + if (skb->len != ATM_CELL_SIZE-1) { + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + return -EINVAL; + } + *(u32 *) skb->data = htonl(*(u32 *) skb->data); + } + submitted++; + ATM_SKB(skb)->vcc = vcc; + tasklet_disable(&ENI_DEV(vcc->dev)->task); + res = do_tx(skb); + tasklet_enable(&ENI_DEV(vcc->dev)->task); + if (res == enq_ok) return 0; + skb_queue_tail(&ENI_VCC(vcc)->tx->backlog,skb); + backlogged++; + tasklet_schedule(&ENI_DEV(vcc->dev)->task); + return 0; +} + +static void eni_phy_put(struct atm_dev *dev,unsigned char value, + unsigned long addr) +{ + writel(value,ENI_DEV(dev)->phy+addr*4); +} + + + +static unsigned char eni_phy_get(struct atm_dev *dev,unsigned long addr) +{ + return readl(ENI_DEV(dev)->phy+addr*4); +} + + +static int eni_proc_read(struct atm_dev *dev,loff_t *pos,char *page) +{ + struct sock *s; + static const char *signal[] = { "LOST","unknown","okay" }; + struct eni_dev *eni_dev = ENI_DEV(dev); + struct atm_vcc *vcc; + int left,i; + + left = *pos; + if (!left) + return sprintf(page,DEV_LABEL "(itf %d) signal %s, %dkB, " + "%d cps remaining\n",dev->number,signal[(int) dev->signal], + eni_dev->mem >> 10,eni_dev->tx_bw); + if (!--left) + return sprintf(page,"%4sBursts: TX" +#if !defined(CONFIG_ATM_ENI_BURST_TX_16W) && \ + !defined(CONFIG_ATM_ENI_BURST_TX_8W) && \ + !defined(CONFIG_ATM_ENI_BURST_TX_4W) && \ + !defined(CONFIG_ATM_ENI_BURST_TX_2W) + " none" +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_16W + " 16W" +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_8W + " 8W" +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_4W + " 4W" +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_2W + " 2W" +#endif + ", RX" +#if !defined(CONFIG_ATM_ENI_BURST_RX_16W) && \ + !defined(CONFIG_ATM_ENI_BURST_RX_8W) && \ + !defined(CONFIG_ATM_ENI_BURST_RX_4W) && \ + !defined(CONFIG_ATM_ENI_BURST_RX_2W) + " none" +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_16W + " 16W" +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_8W + " 8W" +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_4W + " 4W" +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_2W + " 2W" +#endif +#ifndef CONFIG_ATM_ENI_TUNE_BURST + " (default)" +#endif + "\n",""); + if (!--left) + return sprintf(page,"%4sBuffer multipliers: tx %d%%, rx %d%%\n", + "",eni_dev->tx_mult,eni_dev->rx_mult); + for (i = 0; i < NR_CHAN; i++) { + struct eni_tx *tx = eni_dev->tx+i; + + if (!tx->send) continue; + if (!--left) { + return sprintf(page, "tx[%d]: 0x%lx-0x%lx " + "(%6ld bytes), rsv %d cps, shp %d cps%s\n",i, + (unsigned long) (tx->send - eni_dev->ram), + tx->send-eni_dev->ram+tx->words*4-1,tx->words*4, + tx->reserved,tx->shaping, + tx == eni_dev->ubr ? " (UBR)" : ""); + } + if (--left) continue; + return sprintf(page,"%10sbacklog %u packets\n","", + skb_queue_len(&tx->backlog)); + } + read_lock(&vcc_sklist_lock); + for(i = 0; i < VCC_HTABLE_SIZE; ++i) { + struct hlist_head *head = &vcc_hash[i]; + + sk_for_each(s, head) { + struct eni_vcc *eni_vcc; + int length; + + vcc = atm_sk(s); + if (vcc->dev != dev) + continue; + eni_vcc = ENI_VCC(vcc); + if (--left) continue; + length = sprintf(page,"vcc %4d: ",vcc->vci); + if (eni_vcc->rx) { + length += sprintf(page+length, "0x%lx-0x%lx " + "(%6ld bytes)", + (unsigned long) (eni_vcc->recv - eni_dev->ram), + eni_vcc->recv-eni_dev->ram+eni_vcc->words*4-1, + eni_vcc->words*4); + if (eni_vcc->tx) length += sprintf(page+length,", "); + } + if (eni_vcc->tx) + length += sprintf(page+length,"tx[%d], txing %d bytes", + eni_vcc->tx->index,eni_vcc->txing); + page[length] = '\n'; + read_unlock(&vcc_sklist_lock); + return length+1; + } + } + read_unlock(&vcc_sklist_lock); + for (i = 0; i < eni_dev->free_len; i++) { + struct eni_free *fe = eni_dev->free_list+i; + unsigned long offset; + + if (--left) continue; + offset = (unsigned long) eni_dev->ram+eni_dev->base_diff; + return sprintf(page,"free %p-%p (%6d bytes)\n", + fe->start-offset,fe->start-offset+(1 << fe->order)-1, + 1 << fe->order); + } + return 0; +} + + +static const struct atmdev_ops ops = { + .open = eni_open, + .close = eni_close, + .ioctl = eni_ioctl, + .send = eni_send, + .phy_put = eni_phy_put, + .phy_get = eni_phy_get, + .change_qos = eni_change_qos, + .proc_read = eni_proc_read +}; + + +static int eni_init_one(struct pci_dev *pci_dev, + const struct pci_device_id *ent) +{ + struct atm_dev *dev; + struct eni_dev *eni_dev; + struct eni_zero *zero; + int rc; + + rc = pci_enable_device(pci_dev); + if (rc < 0) + goto out; + + rc = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32)); + if (rc < 0) + goto err_disable; + + rc = -ENOMEM; + eni_dev = kmalloc(sizeof(struct eni_dev), GFP_KERNEL); + if (!eni_dev) + goto err_disable; + + zero = &eni_dev->zero; + zero->addr = dma_alloc_coherent(&pci_dev->dev, + ENI_ZEROES_SIZE, &zero->dma, GFP_KERNEL); + if (!zero->addr) + goto err_kfree; + + dev = atm_dev_register(DEV_LABEL, &pci_dev->dev, &ops, -1, NULL); + if (!dev) + goto err_free_consistent; + + dev->dev_data = eni_dev; + pci_set_drvdata(pci_dev, dev); + eni_dev->pci_dev = pci_dev; + eni_dev->asic = ent->driver_data; + + rc = eni_do_init(dev); + if (rc < 0) + goto err_unregister; + + rc = eni_start(dev); + if (rc < 0) + goto err_eni_release; + + eni_dev->more = eni_boards; + eni_boards = dev; +out: + return rc; + +err_eni_release: + dev->phy = NULL; + iounmap(ENI_DEV(dev)->ioaddr); +err_unregister: + atm_dev_deregister(dev); +err_free_consistent: + dma_free_coherent(&pci_dev->dev, ENI_ZEROES_SIZE, zero->addr, zero->dma); +err_kfree: + kfree(eni_dev); +err_disable: + pci_disable_device(pci_dev); + goto out; +} + + +static const struct pci_device_id eni_pci_tbl[] = { + { PCI_VDEVICE(EF, PCI_DEVICE_ID_EF_ATM_FPGA), 0 /* FPGA */ }, + { PCI_VDEVICE(EF, PCI_DEVICE_ID_EF_ATM_ASIC), 1 /* ASIC */ }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci,eni_pci_tbl); + + +static void eni_remove_one(struct pci_dev *pdev) +{ + struct atm_dev *dev = pci_get_drvdata(pdev); + struct eni_dev *ed = ENI_DEV(dev); + struct eni_zero *zero = &ed->zero; + + eni_do_release(dev); + atm_dev_deregister(dev); + dma_free_coherent(&pdev->dev, ENI_ZEROES_SIZE, zero->addr, zero->dma); + kfree(ed); + pci_disable_device(pdev); +} + + +static struct pci_driver eni_driver = { + .name = DEV_LABEL, + .id_table = eni_pci_tbl, + .probe = eni_init_one, + .remove = eni_remove_one, +}; + + +static int __init eni_init(void) +{ + struct sk_buff *skb; /* dummy for sizeof */ + + BUILD_BUG_ON(sizeof(skb->cb) < sizeof(struct eni_skb_prv)); + return pci_register_driver(&eni_driver); +} + + +module_init(eni_init); +/* @@@ since exit routine not defined, this module can not be unloaded */ + +MODULE_LICENSE("GPL"); diff --git a/drivers/atm/eni.h b/drivers/atm/eni.h new file mode 100644 index 000000000..de1ed802c --- /dev/null +++ b/drivers/atm/eni.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/eni.h - Efficient Networks ENI155P device driver declarations */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + + +#ifndef DRIVER_ATM_ENI_H +#define DRIVER_ATM_ENI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "midway.h" + + +#define DEV_LABEL "eni" + +#define UBR_BUFFER (128*1024) /* UBR buffer size */ + +#define RX_DMA_BUF 8 /* burst and skip a few things */ +#define TX_DMA_BUF 100 /* should be enough for 64 kB */ + +#define DEFAULT_RX_MULT 300 /* max_sdu*3 */ +#define DEFAULT_TX_MULT 300 /* max_sdu*3 */ + +#define ENI_ZEROES_SIZE 4 /* need that many DMA-able zero bytes */ + + +struct eni_free { + void __iomem *start; /* counting in bytes */ + int order; +}; + +struct eni_tx { + void __iomem *send; /* base, 0 if unused */ + int prescaler; /* shaping prescaler */ + int resolution; /* shaping divider */ + unsigned long tx_pos; /* current TX write position */ + unsigned long words; /* size of TX queue */ + int index; /* TX channel number */ + int reserved; /* reserved peak cell rate */ + int shaping; /* shaped peak cell rate */ + struct sk_buff_head backlog; /* queue of waiting TX buffers */ +}; + +struct eni_vcc { + int (*rx)(struct atm_vcc *vcc); /* RX function, NULL if none */ + void __iomem *recv; /* receive buffer */ + unsigned long words; /* its size in words */ + unsigned long descr; /* next descriptor (RX) */ + unsigned long rx_pos; /* current RX descriptor pos */ + struct eni_tx *tx; /* TXer, NULL if none */ + int rxing; /* number of pending PDUs */ + int servicing; /* number of waiting VCs (0 or 1) */ + int txing; /* number of pending TX bytes */ + ktime_t timestamp; /* for RX timing */ + struct atm_vcc *next; /* next pending RX */ + struct sk_buff *last; /* last PDU being DMAed (used to carry + discard information) */ +}; + +struct eni_dev { + /*-------------------------------- spinlock */ + spinlock_t lock; /* sync with interrupt */ + struct tasklet_struct task; /* tasklet for interrupt work */ + u32 events; /* pending events */ + /*-------------------------------- base pointers into Midway address + space */ + void __iomem *ioaddr; + void __iomem *phy; /* PHY interface chip registers */ + void __iomem *reg; /* register base */ + void __iomem *ram; /* RAM base */ + void __iomem *vci; /* VCI table */ + void __iomem *rx_dma; /* RX DMA queue */ + void __iomem *tx_dma; /* TX DMA queue */ + void __iomem *service; /* service list */ + /*-------------------------------- TX part */ + struct eni_tx tx[NR_CHAN]; /* TX channels */ + struct eni_tx *ubr; /* UBR channel */ + struct sk_buff_head tx_queue; /* PDUs currently being TX DMAed*/ + wait_queue_head_t tx_wait; /* for close */ + int tx_bw; /* remaining bandwidth */ + u32 dma[TX_DMA_BUF*2]; /* DMA request scratch area */ + struct eni_zero { /* aligned "magic" zeroes */ + u32 *addr; + dma_addr_t dma; + } zero; + int tx_mult; /* buffer size multiplier (percent) */ + /*-------------------------------- RX part */ + u32 serv_read; /* host service read index */ + struct atm_vcc *fast,*last_fast;/* queues of VCCs with pending PDUs */ + struct atm_vcc *slow,*last_slow; + struct atm_vcc **rx_map; /* for fast lookups */ + struct sk_buff_head rx_queue; /* PDUs currently being RX-DMAed */ + wait_queue_head_t rx_wait; /* for close */ + int rx_mult; /* buffer size multiplier (percent) */ + /*-------------------------------- statistics */ + unsigned long lost; /* number of lost cells (RX) */ + /*-------------------------------- memory management */ + unsigned long base_diff; /* virtual-real base address */ + int free_len; /* free list length */ + struct eni_free *free_list; /* free list */ + int free_list_size; /* maximum size of free list */ + /*-------------------------------- ENI links */ + struct atm_dev *more; /* other ENI devices */ + /*-------------------------------- general information */ + int mem; /* RAM on board (in bytes) */ + int asic; /* PCI interface type, 0 for FPGA */ + unsigned int irq; /* IRQ */ + struct pci_dev *pci_dev; /* PCI stuff */ +}; + + +#define ENI_DEV(d) ((struct eni_dev *) (d)->dev_data) +#define ENI_VCC(d) ((struct eni_vcc *) (d)->dev_data) + + +struct eni_skb_prv { + struct atm_skb_data _; /* reserved */ + unsigned long pos; /* position of next descriptor */ + int size; /* PDU size in reassembly buffer */ + dma_addr_t paddr; /* DMA handle */ +}; + +#define ENI_PRV_SIZE(skb) (((struct eni_skb_prv *) (skb)->cb)->size) +#define ENI_PRV_POS(skb) (((struct eni_skb_prv *) (skb)->cb)->pos) +#define ENI_PRV_PADDR(skb) (((struct eni_skb_prv *) (skb)->cb)->paddr) + +#endif diff --git a/drivers/atm/firestream.c b/drivers/atm/firestream.c new file mode 100644 index 000000000..43a34aee3 --- /dev/null +++ b/drivers/atm/firestream.c @@ -0,0 +1,2056 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* drivers/atm/firestream.c - FireStream 155 (MB86697) and + * FireStream 50 (MB86695) device driver + */ + +/* Written & (C) 2000 by R.E.Wolff@BitWizard.nl + * Copied snippets from zatm.c by Werner Almesberger, EPFL LRC/ICA + * and ambassador.c Copyright (C) 1995-1999 Madge Networks Ltd + */ + +/* +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for request_region */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "firestream.h" + +static int loopback = 0; +static int num=0x5a; + +/* According to measurements (but they look suspicious to me!) done in + * '97, 37% of the packets are one cell in size. So it pays to have + * buffers allocated at that size. A large jump in percentage of + * packets occurs at packets around 536 bytes in length. So it also + * pays to have those pre-allocated. Unfortunately, we can't fully + * take advantage of this as the majority of the packets is likely to + * be TCP/IP (As where obviously the measurement comes from) There the + * link would be opened with say a 1500 byte MTU, and we can't handle + * smaller buffers more efficiently than the larger ones. -- REW + */ + +/* Due to the way Linux memory management works, specifying "576" as + * an allocation size here isn't going to help. They are allocated + * from 1024-byte regions anyway. With the size of the sk_buffs (quite + * large), it doesn't pay to allocate the smallest size (64) -- REW */ + +/* This is all guesswork. Hard numbers to back this up or disprove this, + * are appreciated. -- REW */ + +/* The last entry should be about 64k. However, the "buffer size" is + * passed to the chip in a 16 bit field. I don't know how "65536" + * would be interpreted. -- REW */ + +#define NP FS_NR_FREE_POOLS +static int rx_buf_sizes[NP] = {128, 256, 512, 1024, 2048, 4096, 16384, 65520}; +/* log2: 7 8 9 10 11 12 14 16 */ + +#if 0 +static int rx_pool_sizes[NP] = {1024, 1024, 512, 256, 128, 64, 32, 32}; +#else +/* debug */ +static int rx_pool_sizes[NP] = {128, 128, 128, 64, 64, 64, 32, 32}; +#endif +/* log2: 10 10 9 8 7 6 5 5 */ +/* sumlog2: 17 18 18 18 18 18 19 21 */ +/* mem allocated: 128k 256k 256k 256k 256k 256k 512k 2M */ +/* tot mem: almost 4M */ + +/* NP is shorter, so that it fits on a single line. */ +#undef NP + + +/* Small hardware gotcha: + + The FS50 CAM (VP/VC match registers) always take the lowest channel + number that matches. This is not a problem. + + However, they also ignore whether the channel is enabled or + not. This means that if you allocate channel 0 to 1.2 and then + channel 1 to 0.0, then disabeling channel 0 and writing 0 to the + match channel for channel 0 will "steal" the traffic from channel + 1, even if you correctly disable channel 0. + + Workaround: + + - When disabling channels, write an invalid VP/VC value to the + match register. (We use 0xffffffff, which in the worst case + matches VP/VC = /, but I expect it not to match + anything as some "when not in use, program to 0" bits are now + programmed to 1...) + + - Don't initialize the match registers to 0, as 0.0 is a valid + channel. +*/ + + +/* Optimization hints and tips. + + The FireStream chips are very capable of reducing the amount of + "interrupt-traffic" for the CPU. This driver requests an interrupt on EVERY + action. You could try to minimize this a bit. + + Besides that, the userspace->kernel copy and the PCI bus are the + performance limiting issues for this driver. + + You could queue up a bunch of outgoing packets without telling the + FireStream. I'm not sure that's going to win you much though. The + Linux layer won't tell us in advance when it's not going to give us + any more packets in a while. So this is tricky to implement right without + introducing extra delays. + + -- REW + */ + + + + +/* The strings that define what the RX queue entry is all about. */ +/* Fujitsu: Please tell me which ones can have a pointer to a + freepool descriptor! */ +static char *res_strings[] = { + "RX OK: streaming not EOP", + "RX OK: streaming EOP", + "RX OK: Single buffer packet", + "RX OK: packet mode", + "RX OK: F4 OAM (end to end)", + "RX OK: F4 OAM (Segment)", + "RX OK: F5 OAM (end to end)", + "RX OK: F5 OAM (Segment)", + "RX OK: RM cell", + "RX OK: TRANSP cell", + "RX OK: TRANSPC cell", + "Unmatched cell", + "reserved 12", + "reserved 13", + "reserved 14", + "Unrecognized cell", + "reserved 16", + "reassembly abort: AAL5 abort", + "packet purged", + "packet ageing timeout", + "channel ageing timeout", + "calculated length error", + "programmed length limit error", + "aal5 crc32 error", + "oam transp or transpc crc10 error", + "reserved 25", + "reserved 26", + "reserved 27", + "reserved 28", + "reserved 29", + "reserved 30", /* FIXME: The strings between 30-40 might be wrong. */ + "reassembly abort: no buffers", + "receive buffer overflow", + "change in GFC", + "receive buffer full", + "low priority discard - no receive descriptor", + "low priority discard - missing end of packet", + "reserved 37", + "reserved 38", + "reserved 39", + "reserved 40", + "reserved 41", + "reserved 42", + "reserved 43", + "reserved 44", + "reserved 45", + "reserved 46", + "reserved 47", + "reserved 48", + "reserved 49", + "reserved 50", + "reserved 51", + "reserved 52", + "reserved 53", + "reserved 54", + "reserved 55", + "reserved 56", + "reserved 57", + "reserved 58", + "reserved 59", + "reserved 60", + "reserved 61", + "reserved 62", + "reserved 63", +}; + +static char *irq_bitname[] = { + "LPCO", + "DPCO", + "RBRQ0_W", + "RBRQ1_W", + "RBRQ2_W", + "RBRQ3_W", + "RBRQ0_NF", + "RBRQ1_NF", + "RBRQ2_NF", + "RBRQ3_NF", + "BFP_SC", + "INIT", + "INIT_ERR", + "USCEO", + "UPEC0", + "VPFCO", + "CRCCO", + "HECO", + "TBRQ_W", + "TBRQ_NF", + "CTPQ_E", + "GFC_C0", + "PCI_FTL", + "CSQ_W", + "CSQ_NF", + "EXT_INT", + "RXDMA_S" +}; + + +#define PHY_EOF -1 +#define PHY_CLEARALL -2 + +struct reginit_item { + int reg, val; +}; + + +static struct reginit_item PHY_NTC_INIT[] = { + { PHY_CLEARALL, 0x40 }, + { 0x12, 0x0001 }, + { 0x13, 0x7605 }, + { 0x1A, 0x0001 }, + { 0x1B, 0x0005 }, + { 0x38, 0x0003 }, + { 0x39, 0x0006 }, /* changed here to make loopback */ + { 0x01, 0x5262 }, + { 0x15, 0x0213 }, + { 0x00, 0x0003 }, + { PHY_EOF, 0}, /* -1 signals end of list */ +}; + + +/* Safetyfeature: If the card interrupts more than this number of times + in a jiffy (1/100th of a second) then we just disable the interrupt and + print a message. This prevents the system from hanging. + + 150000 packets per second is close to the limit a PC is going to have + anyway. We therefore have to disable this for production. -- REW */ +#undef IRQ_RATE_LIMIT // 100 + +/* Interrupts work now. Unlike serial cards, ATM cards don't work all + that great without interrupts. -- REW */ +#undef FS_POLL_FREQ // 100 + +/* + This driver can spew a whole lot of debugging output at you. If you + need maximum performance, you should disable the DEBUG define. To + aid in debugging in the field, I'm leaving the compile-time debug + features enabled, and disable them "runtime". That allows me to + instruct people with problems to enable debugging without requiring + them to recompile... -- REW +*/ +#define DEBUG + +#ifdef DEBUG +#define fs_dprintk(f, str...) if (fs_debug & f) printk (str) +#else +#define fs_dprintk(f, str...) /* nothing */ +#endif + + +static int fs_keystream = 0; + +#ifdef DEBUG +/* I didn't forget to set this to zero before shipping. Hit me with a stick + if you get this with the debug default not set to zero again. -- REW */ +static int fs_debug = 0; +#else +#define fs_debug 0 +#endif + +#ifdef MODULE +#ifdef DEBUG +module_param(fs_debug, int, 0644); +#endif +module_param(loopback, int, 0); +module_param(num, int, 0); +module_param(fs_keystream, int, 0); +/* XXX Add rx_buf_sizes, and rx_pool_sizes As per request Amar. -- REW */ +#endif + + +#define FS_DEBUG_FLOW 0x00000001 +#define FS_DEBUG_OPEN 0x00000002 +#define FS_DEBUG_QUEUE 0x00000004 +#define FS_DEBUG_IRQ 0x00000008 +#define FS_DEBUG_INIT 0x00000010 +#define FS_DEBUG_SEND 0x00000020 +#define FS_DEBUG_PHY 0x00000040 +#define FS_DEBUG_CLEANUP 0x00000080 +#define FS_DEBUG_QOS 0x00000100 +#define FS_DEBUG_TXQ 0x00000200 +#define FS_DEBUG_ALLOC 0x00000400 +#define FS_DEBUG_TXMEM 0x00000800 +#define FS_DEBUG_QSIZE 0x00001000 + + +#define func_enter() fs_dprintk(FS_DEBUG_FLOW, "fs: enter %s\n", __func__) +#define func_exit() fs_dprintk(FS_DEBUG_FLOW, "fs: exit %s\n", __func__) + + +static struct fs_dev *fs_boards = NULL; + +#ifdef DEBUG + +static void my_hd (void *addr, int len) +{ + int j, ch; + unsigned char *ptr = addr; + + while (len > 0) { + printk ("%p ", ptr); + for (j=0;j < ((len < 16)?len:16);j++) { + printk ("%02x %s", ptr[j], (j==7)?" ":""); + } + for ( ;j < 16;j++) { + printk (" %s", (j==7)?" ":""); + } + for (j=0;j < ((len < 16)?len:16);j++) { + ch = ptr[j]; + printk ("%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch)); + } + printk ("\n"); + ptr += 16; + len -= 16; + } +} +#else /* DEBUG */ +static void my_hd (void *addr, int len){} +#endif /* DEBUG */ + +/********** free an skb (as per ATM device driver documentation) **********/ + +/* Hmm. If this is ATM specific, why isn't there an ATM routine for this? + * I copied it over from the ambassador driver. -- REW */ + +static inline void fs_kfree_skb (struct sk_buff * skb) +{ + if (ATM_SKB(skb)->vcc->pop) + ATM_SKB(skb)->vcc->pop (ATM_SKB(skb)->vcc, skb); + else + dev_kfree_skb_any (skb); +} + + + + +/* It seems the ATM forum recommends this horribly complicated 16bit + * floating point format. Turns out the Ambassador uses the exact same + * encoding. I just copied it over. If Mitch agrees, I'll move it over + * to the atm_misc file or something like that. (and remove it from + * here and the ambassador driver) -- REW + */ + +/* The good thing about this format is that it is monotonic. So, + a conversion routine need not be very complicated. To be able to + round "nearest" we need to take along a few extra bits. Lets + put these after 16 bits, so that we can just return the top 16 + bits of the 32bit number as the result: + + int mr (unsigned int rate, int r) + { + int e = 16+9; + static int round[4]={0, 0, 0xffff, 0x8000}; + if (!rate) return 0; + while (rate & 0xfc000000) { + rate >>= 1; + e++; + } + while (! (rate & 0xfe000000)) { + rate <<= 1; + e--; + } + +// Now the mantissa is in positions bit 16-25. Excepf for the "hidden 1" that's in bit 26. + rate &= ~0x02000000; +// Next add in the exponent + rate |= e << (16+9); +// And perform the rounding: + return (rate + round[r]) >> 16; + } + + 14 lines-of-code. Compare that with the 120 that the Ambassador + guys needed. (would be 8 lines shorter if I'd try to really reduce + the number of lines: + + int mr (unsigned int rate, int r) + { + int e = 16+9; + static int round[4]={0, 0, 0xffff, 0x8000}; + if (!rate) return 0; + for (; rate & 0xfc000000 ;rate >>= 1, e++); + for (;!(rate & 0xfe000000);rate <<= 1, e--); + return ((rate & ~0x02000000) | (e << (16+9)) + round[r]) >> 16; + } + + Exercise for the reader: Remove one more line-of-code, without + cheating. (Just joining two lines is cheating). (I know it's + possible, don't think you've beat me if you found it... If you + manage to lose two lines or more, keep me updated! ;-) + + -- REW */ + + +#define ROUND_UP 1 +#define ROUND_DOWN 2 +#define ROUND_NEAREST 3 +/********** make rate (not quite as much fun as Horizon) **********/ + +static int make_rate(unsigned int rate, int r, + u16 *bits, unsigned int *actual) +{ + unsigned char exp = -1; /* hush gcc */ + unsigned int man = -1; /* hush gcc */ + + fs_dprintk (FS_DEBUG_QOS, "make_rate %u", rate); + + /* rates in cells per second, ITU format (nasty 16-bit floating-point) + given 5-bit e and 9-bit m: + rate = EITHER (1+m/2^9)*2^e OR 0 + bits = EITHER 1<<14 | e<<9 | m OR 0 + (bit 15 is "reserved", bit 14 "non-zero") + smallest rate is 0 (special representation) + largest rate is (1+511/512)*2^31 = 4290772992 (< 2^32-1) + smallest non-zero rate is (1+0/512)*2^0 = 1 (> 0) + simple algorithm: + find position of top bit, this gives e + remove top bit and shift (rounding if feeling clever) by 9-e + */ + /* Ambassador ucode bug: please don't set bit 14! so 0 rate not + representable. // This should move into the ambassador driver + when properly merged. -- REW */ + + if (rate > 0xffc00000U) { + /* larger than largest representable rate */ + + if (r == ROUND_UP) { + return -EINVAL; + } else { + exp = 31; + man = 511; + } + + } else if (rate) { + /* representable rate */ + + exp = 31; + man = rate; + + /* invariant: rate = man*2^(exp-31) */ + while (!(man & (1<<31))) { + exp = exp - 1; + man = man<<1; + } + + /* man has top bit set + rate = (2^31+(man-2^31))*2^(exp-31) + rate = (1+(man-2^31)/2^31)*2^exp + */ + man = man<<1; + man &= 0xffffffffU; /* a nop on 32-bit systems */ + /* rate = (1+man/2^32)*2^exp + + exp is in the range 0 to 31, man is in the range 0 to 2^32-1 + time to lose significance... we want m in the range 0 to 2^9-1 + rounding presents a minor problem... we first decide which way + we are rounding (based on given rounding direction and possibly + the bits of the mantissa that are to be discarded). + */ + + switch (r) { + case ROUND_DOWN: { + /* just truncate */ + man = man>>(32-9); + break; + } + case ROUND_UP: { + /* check all bits that we are discarding */ + if (man & (~0U>>9)) { + man = (man>>(32-9)) + 1; + if (man == (1<<9)) { + /* no need to check for round up outside of range */ + man = 0; + exp += 1; + } + } else { + man = (man>>(32-9)); + } + break; + } + case ROUND_NEAREST: { + /* check msb that we are discarding */ + if (man & (1<<(32-9-1))) { + man = (man>>(32-9)) + 1; + if (man == (1<<9)) { + /* no need to check for round up outside of range */ + man = 0; + exp += 1; + } + } else { + man = (man>>(32-9)); + } + break; + } + } + + } else { + /* zero rate - not representable */ + + if (r == ROUND_DOWN) { + return -EINVAL; + } else { + exp = 0; + man = 0; + } + } + + fs_dprintk (FS_DEBUG_QOS, "rate: man=%u, exp=%hu", man, exp); + + if (bits) + *bits = /* (1<<14) | */ (exp<<9) | man; + + if (actual) + *actual = (exp >= 9) + ? (1 << exp) + (man << (exp-9)) + : (1 << exp) + ((man + (1<<(9-exp-1))) >> (9-exp)); + + return 0; +} + + + + +/* FireStream access routines */ +/* For DEEP-DOWN debugging these can be rigged to intercept accesses to + certain registers or to just log all accesses. */ + +static inline void write_fs (struct fs_dev *dev, int offset, u32 val) +{ + writel (val, dev->base + offset); +} + + +static inline u32 read_fs (struct fs_dev *dev, int offset) +{ + return readl (dev->base + offset); +} + + + +static inline struct FS_QENTRY *get_qentry (struct fs_dev *dev, struct queue *q) +{ + return bus_to_virt (read_fs (dev, Q_WP(q->offset)) & Q_ADDR_MASK); +} + + +static void submit_qentry (struct fs_dev *dev, struct queue *q, struct FS_QENTRY *qe) +{ + u32 wp; + struct FS_QENTRY *cqe; + + /* XXX Sanity check: the write pointer can be checked to be + still the same as the value passed as qe... -- REW */ + /* udelay (5); */ + while ((wp = read_fs (dev, Q_WP (q->offset))) & Q_FULL) { + fs_dprintk (FS_DEBUG_TXQ, "Found queue at %x full. Waiting.\n", + q->offset); + schedule (); + } + + wp &= ~0xf; + cqe = bus_to_virt (wp); + if (qe != cqe) { + fs_dprintk (FS_DEBUG_TXQ, "q mismatch! %p %p\n", qe, cqe); + } + + write_fs (dev, Q_WP(q->offset), Q_INCWRAP); + + { + static int c; + if (!(c++ % 100)) + { + int rp, wp; + rp = read_fs (dev, Q_RP(q->offset)); + wp = read_fs (dev, Q_WP(q->offset)); + fs_dprintk (FS_DEBUG_TXQ, "q at %d: %x-%x: %x entries.\n", + q->offset, rp, wp, wp-rp); + } + } +} + +#ifdef DEBUG_EXTRA +static struct FS_QENTRY pq[60]; +static int qp; + +static struct FS_BPENTRY dq[60]; +static int qd; +static void *da[60]; +#endif + +static void submit_queue (struct fs_dev *dev, struct queue *q, + u32 cmd, u32 p1, u32 p2, u32 p3) +{ + struct FS_QENTRY *qe; + + qe = get_qentry (dev, q); + qe->cmd = cmd; + qe->p0 = p1; + qe->p1 = p2; + qe->p2 = p3; + submit_qentry (dev, q, qe); + +#ifdef DEBUG_EXTRA + pq[qp].cmd = cmd; + pq[qp].p0 = p1; + pq[qp].p1 = p2; + pq[qp].p2 = p3; + qp++; + if (qp >= 60) qp = 0; +#endif +} + +/* Test the "other" way one day... -- REW */ +#if 1 +#define submit_command submit_queue +#else + +static void submit_command (struct fs_dev *dev, struct queue *q, + u32 cmd, u32 p1, u32 p2, u32 p3) +{ + write_fs (dev, CMDR0, cmd); + write_fs (dev, CMDR1, p1); + write_fs (dev, CMDR2, p2); + write_fs (dev, CMDR3, p3); +} +#endif + + + +static void process_return_queue (struct fs_dev *dev, struct queue *q) +{ + long rq; + struct FS_QENTRY *qe; + void *tc; + + while (!((rq = read_fs (dev, Q_RP(q->offset))) & Q_EMPTY)) { + fs_dprintk (FS_DEBUG_QUEUE, "reaping return queue entry at %lx\n", rq); + qe = bus_to_virt (rq); + + fs_dprintk (FS_DEBUG_QUEUE, "queue entry: %08x %08x %08x %08x. (%d)\n", + qe->cmd, qe->p0, qe->p1, qe->p2, STATUS_CODE (qe)); + + switch (STATUS_CODE (qe)) { + case 5: + tc = bus_to_virt (qe->p0); + fs_dprintk (FS_DEBUG_ALLOC, "Free tc: %p\n", tc); + kfree (tc); + break; + } + + write_fs (dev, Q_RP(q->offset), Q_INCWRAP); + } +} + + +static void process_txdone_queue (struct fs_dev *dev, struct queue *q) +{ + long rq; + long tmp; + struct FS_QENTRY *qe; + struct sk_buff *skb; + struct FS_BPENTRY *td; + + while (!((rq = read_fs (dev, Q_RP(q->offset))) & Q_EMPTY)) { + fs_dprintk (FS_DEBUG_QUEUE, "reaping txdone entry at %lx\n", rq); + qe = bus_to_virt (rq); + + fs_dprintk (FS_DEBUG_QUEUE, "queue entry: %08x %08x %08x %08x: %d\n", + qe->cmd, qe->p0, qe->p1, qe->p2, STATUS_CODE (qe)); + + if (STATUS_CODE (qe) != 2) + fs_dprintk (FS_DEBUG_TXMEM, "queue entry: %08x %08x %08x %08x: %d\n", + qe->cmd, qe->p0, qe->p1, qe->p2, STATUS_CODE (qe)); + + + switch (STATUS_CODE (qe)) { + case 0x01: /* This is for AAL0 where we put the chip in streaming mode */ + fallthrough; + case 0x02: + /* Process a real txdone entry. */ + tmp = qe->p0; + if (tmp & 0x0f) + printk (KERN_WARNING "td not aligned: %ld\n", tmp); + tmp &= ~0x0f; + td = bus_to_virt (tmp); + + fs_dprintk (FS_DEBUG_QUEUE, "Pool entry: %08x %08x %08x %08x %p.\n", + td->flags, td->next, td->bsa, td->aal_bufsize, td->skb ); + + skb = td->skb; + if (skb == FS_VCC (ATM_SKB(skb)->vcc)->last_skb) { + FS_VCC (ATM_SKB(skb)->vcc)->last_skb = NULL; + wake_up_interruptible (& FS_VCC (ATM_SKB(skb)->vcc)->close_wait); + } + td->dev->ntxpckts--; + + { + static int c=0; + + if (!(c++ % 100)) { + fs_dprintk (FS_DEBUG_QSIZE, "[%d]", td->dev->ntxpckts); + } + } + + atomic_inc(&ATM_SKB(skb)->vcc->stats->tx); + + fs_dprintk (FS_DEBUG_TXMEM, "i"); + fs_dprintk (FS_DEBUG_ALLOC, "Free t-skb: %p\n", skb); + fs_kfree_skb (skb); + + fs_dprintk (FS_DEBUG_ALLOC, "Free trans-d: %p\n", td); + memset (td, ATM_POISON_FREE, sizeof(struct FS_BPENTRY)); + kfree (td); + break; + default: + /* Here we get the tx purge inhibit command ... */ + /* Action, I believe, is "don't do anything". -- REW */ + ; + } + + write_fs (dev, Q_RP(q->offset), Q_INCWRAP); + } +} + + +static void process_incoming (struct fs_dev *dev, struct queue *q) +{ + long rq; + struct FS_QENTRY *qe; + struct FS_BPENTRY *pe; + struct sk_buff *skb; + unsigned int channo; + struct atm_vcc *atm_vcc; + + while (!((rq = read_fs (dev, Q_RP(q->offset))) & Q_EMPTY)) { + fs_dprintk (FS_DEBUG_QUEUE, "reaping incoming queue entry at %lx\n", rq); + qe = bus_to_virt (rq); + + fs_dprintk (FS_DEBUG_QUEUE, "queue entry: %08x %08x %08x %08x. ", + qe->cmd, qe->p0, qe->p1, qe->p2); + + fs_dprintk (FS_DEBUG_QUEUE, "-> %x: %s\n", + STATUS_CODE (qe), + res_strings[STATUS_CODE(qe)]); + + pe = bus_to_virt (qe->p0); + fs_dprintk (FS_DEBUG_QUEUE, "Pool entry: %08x %08x %08x %08x %p %p.\n", + pe->flags, pe->next, pe->bsa, pe->aal_bufsize, + pe->skb, pe->fp); + + channo = qe->cmd & 0xffff; + + if (channo < dev->nchannels) + atm_vcc = dev->atm_vccs[channo]; + else + atm_vcc = NULL; + + /* Single buffer packet */ + switch (STATUS_CODE (qe)) { + case 0x1: + /* Fall through for streaming mode */ + case 0x2:/* Packet received OK.... */ + if (atm_vcc) { + skb = pe->skb; + pe->fp->n--; +#if 0 + fs_dprintk (FS_DEBUG_QUEUE, "Got skb: %p\n", skb); + if (FS_DEBUG_QUEUE & fs_debug) my_hd (bus_to_virt (pe->bsa), 0x20); +#endif + skb_put (skb, qe->p1 & 0xffff); + ATM_SKB(skb)->vcc = atm_vcc; + atomic_inc(&atm_vcc->stats->rx); + __net_timestamp(skb); + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p (pushed)\n", skb); + atm_vcc->push (atm_vcc, skb); + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", pe); + kfree (pe); + } else { + printk (KERN_ERR "Got a receive on a non-open channel %d.\n", channo); + } + break; + case 0x17:/* AAL 5 CRC32 error. IFF the length field is nonzero, a buffer + has been consumed and needs to be processed. -- REW */ + if (qe->p1 & 0xffff) { + pe = bus_to_virt (qe->p0); + pe->fp->n--; + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", pe->skb); + dev_kfree_skb_any (pe->skb); + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", pe); + kfree (pe); + } + if (atm_vcc) + atomic_inc(&atm_vcc->stats->rx_drop); + break; + case 0x1f: /* Reassembly abort: no buffers. */ + /* Silently increment error counter. */ + if (atm_vcc) + atomic_inc(&atm_vcc->stats->rx_drop); + break; + default: /* Hmm. Haven't written the code to handle the others yet... -- REW */ + printk (KERN_WARNING "Don't know what to do with RX status %x: %s.\n", + STATUS_CODE(qe), res_strings[STATUS_CODE (qe)]); + } + write_fs (dev, Q_RP(q->offset), Q_INCWRAP); + } +} + + + +#define DO_DIRECTION(tp) ((tp)->traffic_class != ATM_NONE) + +static int fs_open(struct atm_vcc *atm_vcc) +{ + struct fs_dev *dev; + struct fs_vcc *vcc; + struct fs_transmit_config *tc; + struct atm_trafprm * txtp; + struct atm_trafprm * rxtp; + /* struct fs_receive_config *rc;*/ + /* struct FS_QENTRY *qe; */ + int error; + int bfp; + int to; + unsigned short tmc0; + short vpi = atm_vcc->vpi; + int vci = atm_vcc->vci; + + func_enter (); + + dev = FS_DEV(atm_vcc->dev); + fs_dprintk (FS_DEBUG_OPEN, "fs: open on dev: %p, vcc at %p\n", + dev, atm_vcc); + + if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC) + set_bit(ATM_VF_ADDR, &atm_vcc->flags); + + if ((atm_vcc->qos.aal != ATM_AAL5) && + (atm_vcc->qos.aal != ATM_AAL2)) + return -EINVAL; /* XXX AAL0 */ + + fs_dprintk (FS_DEBUG_OPEN, "fs: (itf %d): open %d.%d\n", + atm_vcc->dev->number, atm_vcc->vpi, atm_vcc->vci); + + /* XXX handle qos parameters (rate limiting) ? */ + + vcc = kmalloc(sizeof(struct fs_vcc), GFP_KERNEL); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc VCC: %p(%zd)\n", vcc, sizeof(struct fs_vcc)); + if (!vcc) { + clear_bit(ATM_VF_ADDR, &atm_vcc->flags); + return -ENOMEM; + } + + atm_vcc->dev_data = vcc; + vcc->last_skb = NULL; + + init_waitqueue_head (&vcc->close_wait); + + txtp = &atm_vcc->qos.txtp; + rxtp = &atm_vcc->qos.rxtp; + + if (!test_bit(ATM_VF_PARTIAL, &atm_vcc->flags)) { + if (IS_FS50(dev)) { + /* Increment the channel numer: take a free one next time. */ + for (to=33;to;to--, dev->channo++) { + /* We only have 32 channels */ + if (dev->channo >= 32) + dev->channo = 0; + /* If we need to do RX, AND the RX is inuse, try the next */ + if (DO_DIRECTION(rxtp) && dev->atm_vccs[dev->channo]) + continue; + /* If we need to do TX, AND the TX is inuse, try the next */ + if (DO_DIRECTION(txtp) && test_bit (dev->channo, dev->tx_inuse)) + continue; + /* Ok, both are free! (or not needed) */ + break; + } + if (!to) { + printk ("No more free channels for FS50..\n"); + kfree(vcc); + return -EBUSY; + } + vcc->channo = dev->channo; + dev->channo &= dev->channel_mask; + + } else { + vcc->channo = (vpi << FS155_VCI_BITS) | (vci); + if (((DO_DIRECTION(rxtp) && dev->atm_vccs[vcc->channo])) || + ( DO_DIRECTION(txtp) && test_bit (vcc->channo, dev->tx_inuse))) { + printk ("Channel is in use for FS155.\n"); + kfree(vcc); + return -EBUSY; + } + } + fs_dprintk (FS_DEBUG_OPEN, "OK. Allocated channel %x(%d).\n", + vcc->channo, vcc->channo); + } + + if (DO_DIRECTION (txtp)) { + tc = kmalloc (sizeof (struct fs_transmit_config), GFP_KERNEL); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc tc: %p(%zd)\n", + tc, sizeof (struct fs_transmit_config)); + if (!tc) { + fs_dprintk (FS_DEBUG_OPEN, "fs: can't alloc transmit_config.\n"); + kfree(vcc); + return -ENOMEM; + } + + /* Allocate the "open" entry from the high priority txq. This makes + it most likely that the chip will notice it. It also prevents us + from having to wait for completion. On the other hand, we may + need to wait for completion anyway, to see if it completed + successfully. */ + + switch (atm_vcc->qos.aal) { + case ATM_AAL2: + case ATM_AAL0: + tc->flags = 0 + | TC_FLAGS_TRANSPARENT_PAYLOAD + | TC_FLAGS_PACKET + | (1 << 28) + | TC_FLAGS_TYPE_UBR /* XXX Change to VBR -- PVDL */ + | TC_FLAGS_CAL0; + break; + case ATM_AAL5: + tc->flags = 0 + | TC_FLAGS_AAL5 + | TC_FLAGS_PACKET /* ??? */ + | TC_FLAGS_TYPE_CBR + | TC_FLAGS_CAL0; + break; + default: + printk ("Unknown aal: %d\n", atm_vcc->qos.aal); + tc->flags = 0; + } + /* Docs are vague about this atm_hdr field. By the way, the FS + * chip makes odd errors if lower bits are set.... -- REW */ + tc->atm_hdr = (vpi << 20) | (vci << 4); + tmc0 = 0; + { + int pcr = atm_pcr_goal (txtp); + + fs_dprintk (FS_DEBUG_OPEN, "pcr = %d.\n", pcr); + + /* XXX Hmm. officially we're only allowed to do this if rounding + is round_down -- REW */ + if (IS_FS50(dev)) { + if (pcr > 51840000/53/8) pcr = 51840000/53/8; + } else { + if (pcr > 155520000/53/8) pcr = 155520000/53/8; + } + if (!pcr) { + /* no rate cap */ + tmc0 = IS_FS50(dev)?0x61BE:0x64c9; /* Just copied over the bits from Fujitsu -- REW */ + } else { + int r; + if (pcr < 0) { + r = ROUND_DOWN; + pcr = -pcr; + } else { + r = ROUND_UP; + } + error = make_rate (pcr, r, &tmc0, NULL); + if (error) { + kfree(tc); + kfree(vcc); + return error; + } + } + fs_dprintk (FS_DEBUG_OPEN, "pcr = %d.\n", pcr); + } + + tc->TMC[0] = tmc0 | 0x4000; + tc->TMC[1] = 0; /* Unused */ + tc->TMC[2] = 0; /* Unused */ + tc->TMC[3] = 0; /* Unused */ + + tc->spec = 0; /* UTOPIA address, UDF, HEC: Unused -> 0 */ + tc->rtag[0] = 0; /* What should I do with routing tags??? + -- Not used -- AS -- Thanks -- REW*/ + tc->rtag[1] = 0; + tc->rtag[2] = 0; + + if (fs_debug & FS_DEBUG_OPEN) { + fs_dprintk (FS_DEBUG_OPEN, "TX config record:\n"); + my_hd (tc, sizeof (*tc)); + } + + /* We now use the "submit_command" function to submit commands to + the firestream. There is a define up near the definition of + that routine that switches this routine between immediate write + to the immediate command registers and queuing the commands in + the HPTXQ for execution. This last technique might be more + efficient if we know we're going to submit a whole lot of + commands in one go, but this driver is not setup to be able to + use such a construct. So it probably doen't matter much right + now. -- REW */ + + /* The command is IMMediate and INQueue. The parameters are out-of-line.. */ + submit_command (dev, &dev->hp_txq, + QE_CMD_CONFIG_TX | QE_CMD_IMM_INQ | vcc->channo, + virt_to_bus (tc), 0, 0); + + submit_command (dev, &dev->hp_txq, + QE_CMD_TX_EN | QE_CMD_IMM_INQ | vcc->channo, + 0, 0, 0); + set_bit (vcc->channo, dev->tx_inuse); + } + + if (DO_DIRECTION (rxtp)) { + dev->atm_vccs[vcc->channo] = atm_vcc; + + for (bfp = 0;bfp < FS_NR_FREE_POOLS; bfp++) + if (atm_vcc->qos.rxtp.max_sdu <= dev->rx_fp[bfp].bufsize) break; + if (bfp >= FS_NR_FREE_POOLS) { + fs_dprintk (FS_DEBUG_OPEN, "No free pool fits sdu: %d.\n", + atm_vcc->qos.rxtp.max_sdu); + /* XXX Cleanup? -- Would just calling fs_close work??? -- REW */ + + /* XXX clear tx inuse. Close TX part? */ + dev->atm_vccs[vcc->channo] = NULL; + kfree (vcc); + return -EINVAL; + } + + switch (atm_vcc->qos.aal) { + case ATM_AAL0: + case ATM_AAL2: + submit_command (dev, &dev->hp_txq, + QE_CMD_CONFIG_RX | QE_CMD_IMM_INQ | vcc->channo, + RC_FLAGS_TRANSP | + RC_FLAGS_BFPS_BFP * bfp | + RC_FLAGS_RXBM_PSB, 0, 0); + break; + case ATM_AAL5: + submit_command (dev, &dev->hp_txq, + QE_CMD_CONFIG_RX | QE_CMD_IMM_INQ | vcc->channo, + RC_FLAGS_AAL5 | + RC_FLAGS_BFPS_BFP * bfp | + RC_FLAGS_RXBM_PSB, 0, 0); + break; + } + if (IS_FS50 (dev)) { + submit_command (dev, &dev->hp_txq, + QE_CMD_REG_WR | QE_CMD_IMM_INQ, + 0x80 + vcc->channo, + (vpi << 16) | vci, 0 ); /* XXX -- Use defines. */ + } + submit_command (dev, &dev->hp_txq, + QE_CMD_RX_EN | QE_CMD_IMM_INQ | vcc->channo, + 0, 0, 0); + } + + /* Indicate we're done! */ + set_bit(ATM_VF_READY, &atm_vcc->flags); + + func_exit (); + return 0; +} + + +static void fs_close(struct atm_vcc *atm_vcc) +{ + struct fs_dev *dev = FS_DEV (atm_vcc->dev); + struct fs_vcc *vcc = FS_VCC (atm_vcc); + struct atm_trafprm * txtp; + struct atm_trafprm * rxtp; + + func_enter (); + + clear_bit(ATM_VF_READY, &atm_vcc->flags); + + fs_dprintk (FS_DEBUG_QSIZE, "--==**[%d]**==--", dev->ntxpckts); + if (vcc->last_skb) { + fs_dprintk (FS_DEBUG_QUEUE, "Waiting for skb %p to be sent.\n", + vcc->last_skb); + /* We're going to wait for the last packet to get sent on this VC. It would + be impolite not to send them don't you think? + XXX + We don't know which packets didn't get sent. So if we get interrupted in + this sleep_on, we'll lose any reference to these packets. Memory leak! + On the other hand, it's awfully convenient that we can abort a "close" that + is taking too long. Maybe just use non-interruptible sleep on? -- REW */ + wait_event_interruptible(vcc->close_wait, !vcc->last_skb); + } + + txtp = &atm_vcc->qos.txtp; + rxtp = &atm_vcc->qos.rxtp; + + + /* See App note XXX (Unpublished as of now) for the reason for the + removal of the "CMD_IMM_INQ" part of the TX_PURGE_INH... -- REW */ + + if (DO_DIRECTION (txtp)) { + submit_command (dev, &dev->hp_txq, + QE_CMD_TX_PURGE_INH | /*QE_CMD_IMM_INQ|*/ vcc->channo, 0,0,0); + clear_bit (vcc->channo, dev->tx_inuse); + } + + if (DO_DIRECTION (rxtp)) { + submit_command (dev, &dev->hp_txq, + QE_CMD_RX_PURGE_INH | QE_CMD_IMM_INQ | vcc->channo, 0,0,0); + dev->atm_vccs [vcc->channo] = NULL; + + /* This means that this is configured as a receive channel */ + if (IS_FS50 (dev)) { + /* Disable the receive filter. Is 0/0 indeed an invalid receive + channel? -- REW. Yes it is. -- Hang. Ok. I'll use -1 + (0xfff...) -- REW */ + submit_command (dev, &dev->hp_txq, + QE_CMD_REG_WR | QE_CMD_IMM_INQ, + 0x80 + vcc->channo, -1, 0 ); + } + } + + fs_dprintk (FS_DEBUG_ALLOC, "Free vcc: %p\n", vcc); + kfree (vcc); + + func_exit (); +} + + +static int fs_send (struct atm_vcc *atm_vcc, struct sk_buff *skb) +{ + struct fs_dev *dev = FS_DEV (atm_vcc->dev); + struct fs_vcc *vcc = FS_VCC (atm_vcc); + struct FS_BPENTRY *td; + + func_enter (); + + fs_dprintk (FS_DEBUG_TXMEM, "I"); + fs_dprintk (FS_DEBUG_SEND, "Send: atm_vcc %p skb %p vcc %p dev %p\n", + atm_vcc, skb, vcc, dev); + + fs_dprintk (FS_DEBUG_ALLOC, "Alloc t-skb: %p (atm_send)\n", skb); + + ATM_SKB(skb)->vcc = atm_vcc; + + vcc->last_skb = skb; + + td = kmalloc (sizeof (struct FS_BPENTRY), GFP_ATOMIC); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc transd: %p(%zd)\n", td, sizeof (struct FS_BPENTRY)); + if (!td) { + /* Oops out of mem */ + return -ENOMEM; + } + + fs_dprintk (FS_DEBUG_SEND, "first word in buffer: %x\n", + *(int *) skb->data); + + td->flags = TD_EPI | TD_DATA | skb->len; + td->next = 0; + td->bsa = virt_to_bus (skb->data); + td->skb = skb; + td->dev = dev; + dev->ntxpckts++; + +#ifdef DEBUG_EXTRA + da[qd] = td; + dq[qd].flags = td->flags; + dq[qd].next = td->next; + dq[qd].bsa = td->bsa; + dq[qd].skb = td->skb; + dq[qd].dev = td->dev; + qd++; + if (qd >= 60) qd = 0; +#endif + + submit_queue (dev, &dev->hp_txq, + QE_TRANSMIT_DE | vcc->channo, + virt_to_bus (td), 0, + virt_to_bus (td)); + + fs_dprintk (FS_DEBUG_QUEUE, "in send: txq %d txrq %d\n", + read_fs (dev, Q_EA (dev->hp_txq.offset)) - + read_fs (dev, Q_SA (dev->hp_txq.offset)), + read_fs (dev, Q_EA (dev->tx_relq.offset)) - + read_fs (dev, Q_SA (dev->tx_relq.offset))); + + func_exit (); + return 0; +} + + +/* Some function placeholders for functions we don't yet support. */ + +#if 0 +static int fs_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + func_enter (); + func_exit (); + return -ENOIOCTLCMD; +} + + +static int fs_getsockopt(struct atm_vcc *vcc,int level,int optname, + void __user *optval,int optlen) +{ + func_enter (); + func_exit (); + return 0; +} + + +static int fs_setsockopt(struct atm_vcc *vcc,int level,int optname, + void __user *optval,unsigned int optlen) +{ + func_enter (); + func_exit (); + return 0; +} + + +static void fs_phy_put(struct atm_dev *dev,unsigned char value, + unsigned long addr) +{ + func_enter (); + func_exit (); +} + + +static unsigned char fs_phy_get(struct atm_dev *dev,unsigned long addr) +{ + func_enter (); + func_exit (); + return 0; +} + + +static int fs_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flags) +{ + func_enter (); + func_exit (); + return 0; +}; + +#endif + + +static const struct atmdev_ops ops = { + .open = fs_open, + .close = fs_close, + .send = fs_send, + .owner = THIS_MODULE, + /* ioctl: fs_ioctl, */ + /* change_qos: fs_change_qos, */ + + /* For now implement these internally here... */ + /* phy_put: fs_phy_put, */ + /* phy_get: fs_phy_get, */ +}; + + +static void undocumented_pci_fix(struct pci_dev *pdev) +{ + u32 tint; + + /* The Windows driver says: */ + /* Switch off FireStream Retry Limit Threshold + */ + + /* The register at 0x28 is documented as "reserved", no further + comments. */ + + pci_read_config_dword (pdev, 0x28, &tint); + if (tint != 0x80) { + tint = 0x80; + pci_write_config_dword (pdev, 0x28, tint); + } +} + + + +/************************************************************************** + * PHY routines * + **************************************************************************/ + +static void write_phy(struct fs_dev *dev, int regnum, int val) +{ + submit_command (dev, &dev->hp_txq, QE_CMD_PRP_WR | QE_CMD_IMM_INQ, + regnum, val, 0); +} + +static int init_phy(struct fs_dev *dev, struct reginit_item *reginit) +{ + int i; + + func_enter (); + while (reginit->reg != PHY_EOF) { + if (reginit->reg == PHY_CLEARALL) { + /* "PHY_CLEARALL means clear all registers. Numregisters is in "val". */ + for (i=0;ival;i++) { + write_phy (dev, i, 0); + } + } else { + write_phy (dev, reginit->reg, reginit->val); + } + reginit++; + } + func_exit (); + return 0; +} + +static void reset_chip (struct fs_dev *dev) +{ + int i; + + write_fs (dev, SARMODE0, SARMODE0_SRTS0); + + /* Undocumented delay */ + udelay (128); + + /* The "internal registers are documented to all reset to zero, but + comments & code in the Windows driver indicates that the pools are + NOT reset. */ + for (i=0;i < FS_NR_FREE_POOLS;i++) { + write_fs (dev, FP_CNF (RXB_FP(i)), 0); + write_fs (dev, FP_SA (RXB_FP(i)), 0); + write_fs (dev, FP_EA (RXB_FP(i)), 0); + write_fs (dev, FP_CNT (RXB_FP(i)), 0); + write_fs (dev, FP_CTU (RXB_FP(i)), 0); + } + + /* The same goes for the match channel registers, although those are + NOT documented that way in the Windows driver. -- REW */ + /* The Windows driver DOES write 0 to these registers somewhere in + the init sequence. However, a small hardware-feature, will + prevent reception of data on VPI/VCI = 0/0 (Unless the channel + allocated happens to have no disabled channels that have a lower + number. -- REW */ + + /* Clear the match channel registers. */ + if (IS_FS50 (dev)) { + for (i=0;i 0x10 alignment not yet implemented (hard!)\n"); + return NULL; +} + +static int init_q(struct fs_dev *dev, struct queue *txq, int queue, + int nentries, int is_rq) +{ + int sz = nentries * sizeof (struct FS_QENTRY); + struct FS_QENTRY *p; + + func_enter (); + + fs_dprintk (FS_DEBUG_INIT, "Initializing queue at %x: %d entries:\n", + queue, nentries); + + p = aligned_kmalloc (sz, GFP_KERNEL, 0x10); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc queue: %p(%d)\n", p, sz); + + if (!p) return 0; + + write_fs (dev, Q_SA(queue), virt_to_bus(p)); + write_fs (dev, Q_EA(queue), virt_to_bus(p+nentries-1)); + write_fs (dev, Q_WP(queue), virt_to_bus(p)); + write_fs (dev, Q_RP(queue), virt_to_bus(p)); + if (is_rq) { + /* Configuration for the receive queue: 0: interrupt immediately, + no pre-warning to empty queues: We do our best to keep the + queue filled anyway. */ + write_fs (dev, Q_CNF(queue), 0 ); + } + + txq->sa = p; + txq->ea = p; + txq->offset = queue; + + func_exit (); + return 1; +} + + +static int init_fp(struct fs_dev *dev, struct freepool *fp, int queue, + int bufsize, int nr_buffers) +{ + func_enter (); + + fs_dprintk (FS_DEBUG_INIT, "Initializing free pool at %x:\n", queue); + + write_fs (dev, FP_CNF(queue), (bufsize * RBFP_RBS) | RBFP_RBSVAL | RBFP_CME); + write_fs (dev, FP_SA(queue), 0); + write_fs (dev, FP_EA(queue), 0); + write_fs (dev, FP_CTU(queue), 0); + write_fs (dev, FP_CNT(queue), 0); + + fp->offset = queue; + fp->bufsize = bufsize; + fp->nr_buffers = nr_buffers; + + func_exit (); + return 1; +} + + +static inline int nr_buffers_in_freepool (struct fs_dev *dev, struct freepool *fp) +{ +#if 0 + /* This seems to be unreliable.... */ + return read_fs (dev, FP_CNT (fp->offset)); +#else + return fp->n; +#endif +} + + +/* Check if this gets going again if a pool ever runs out. -- Yes, it + does. I've seen "receive abort: no buffers" and things started + working again after that... -- REW */ + +static void top_off_fp (struct fs_dev *dev, struct freepool *fp, + gfp_t gfp_flags) +{ + struct FS_BPENTRY *qe, *ne; + struct sk_buff *skb; + int n = 0; + u32 qe_tmp; + + fs_dprintk (FS_DEBUG_QUEUE, "Topping off queue at %x (%d-%d/%d)\n", + fp->offset, read_fs (dev, FP_CNT (fp->offset)), fp->n, + fp->nr_buffers); + while (nr_buffers_in_freepool(dev, fp) < fp->nr_buffers) { + + skb = alloc_skb (fp->bufsize, gfp_flags); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc rec-skb: %p(%d)\n", skb, fp->bufsize); + if (!skb) break; + ne = kmalloc (sizeof (struct FS_BPENTRY), gfp_flags); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc rec-d: %p(%zd)\n", ne, sizeof (struct FS_BPENTRY)); + if (!ne) { + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", skb); + dev_kfree_skb_any (skb); + break; + } + + fs_dprintk (FS_DEBUG_QUEUE, "Adding skb %p desc %p -> %p(%p) ", + skb, ne, skb->data, skb->head); + n++; + ne->flags = FP_FLAGS_EPI | fp->bufsize; + ne->next = virt_to_bus (NULL); + ne->bsa = virt_to_bus (skb->data); + ne->aal_bufsize = fp->bufsize; + ne->skb = skb; + ne->fp = fp; + + /* + * FIXME: following code encodes and decodes + * machine pointers (could be 64-bit) into a + * 32-bit register. + */ + + qe_tmp = read_fs (dev, FP_EA(fp->offset)); + fs_dprintk (FS_DEBUG_QUEUE, "link at %x\n", qe_tmp); + if (qe_tmp) { + qe = bus_to_virt ((long) qe_tmp); + qe->next = virt_to_bus(ne); + qe->flags &= ~FP_FLAGS_EPI; + } else + write_fs (dev, FP_SA(fp->offset), virt_to_bus(ne)); + + write_fs (dev, FP_EA(fp->offset), virt_to_bus (ne)); + fp->n++; /* XXX Atomic_inc? */ + write_fs (dev, FP_CTU(fp->offset), 1); + } + + fs_dprintk (FS_DEBUG_QUEUE, "Added %d entries. \n", n); +} + +static void free_queue(struct fs_dev *dev, struct queue *txq) +{ + func_enter (); + + write_fs (dev, Q_SA(txq->offset), 0); + write_fs (dev, Q_EA(txq->offset), 0); + write_fs (dev, Q_RP(txq->offset), 0); + write_fs (dev, Q_WP(txq->offset), 0); + /* Configuration ? */ + + fs_dprintk (FS_DEBUG_ALLOC, "Free queue: %p\n", txq->sa); + kfree (txq->sa); + + func_exit (); +} + +static void free_freepool(struct fs_dev *dev, struct freepool *fp) +{ + func_enter (); + + write_fs (dev, FP_CNF(fp->offset), 0); + write_fs (dev, FP_SA (fp->offset), 0); + write_fs (dev, FP_EA (fp->offset), 0); + write_fs (dev, FP_CNT(fp->offset), 0); + write_fs (dev, FP_CTU(fp->offset), 0); + + func_exit (); +} + + + +static irqreturn_t fs_irq (int irq, void *dev_id) +{ + int i; + u32 status; + struct fs_dev *dev = dev_id; + + status = read_fs (dev, ISR); + if (!status) + return IRQ_NONE; + + func_enter (); + +#ifdef IRQ_RATE_LIMIT + /* Aaargh! I'm ashamed. This costs more lines-of-code than the actual + interrupt routine!. (Well, used to when I wrote that comment) -- REW */ + { + static int lastjif; + static int nintr=0; + + if (lastjif == jiffies) { + if (++nintr > IRQ_RATE_LIMIT) { + free_irq (dev->irq, dev_id); + printk (KERN_ERR "fs: Too many interrupts. Turning off interrupt %d.\n", + dev->irq); + } + } else { + lastjif = jiffies; + nintr = 0; + } + } +#endif + fs_dprintk (FS_DEBUG_QUEUE, "in intr: txq %d txrq %d\n", + read_fs (dev, Q_EA (dev->hp_txq.offset)) - + read_fs (dev, Q_SA (dev->hp_txq.offset)), + read_fs (dev, Q_EA (dev->tx_relq.offset)) - + read_fs (dev, Q_SA (dev->tx_relq.offset))); + + /* print the bits in the ISR register. */ + if (fs_debug & FS_DEBUG_IRQ) { + /* The FS_DEBUG things are unnecessary here. But this way it is + clear for grep that these are debug prints. */ + fs_dprintk (FS_DEBUG_IRQ, "IRQ status:"); + for (i=0;i<27;i++) + if (status & (1 << i)) + fs_dprintk (FS_DEBUG_IRQ, " %s", irq_bitname[i]); + fs_dprintk (FS_DEBUG_IRQ, "\n"); + } + + if (status & ISR_RBRQ0_W) { + fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (0)!!!!\n"); + process_incoming (dev, &dev->rx_rq[0]); + /* items mentioned on RBRQ0 are from FP 0 or 1. */ + top_off_fp (dev, &dev->rx_fp[0], GFP_ATOMIC); + top_off_fp (dev, &dev->rx_fp[1], GFP_ATOMIC); + } + + if (status & ISR_RBRQ1_W) { + fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (1)!!!!\n"); + process_incoming (dev, &dev->rx_rq[1]); + top_off_fp (dev, &dev->rx_fp[2], GFP_ATOMIC); + top_off_fp (dev, &dev->rx_fp[3], GFP_ATOMIC); + } + + if (status & ISR_RBRQ2_W) { + fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (2)!!!!\n"); + process_incoming (dev, &dev->rx_rq[2]); + top_off_fp (dev, &dev->rx_fp[4], GFP_ATOMIC); + top_off_fp (dev, &dev->rx_fp[5], GFP_ATOMIC); + } + + if (status & ISR_RBRQ3_W) { + fs_dprintk (FS_DEBUG_IRQ, "Iiiin-coming (3)!!!!\n"); + process_incoming (dev, &dev->rx_rq[3]); + top_off_fp (dev, &dev->rx_fp[6], GFP_ATOMIC); + top_off_fp (dev, &dev->rx_fp[7], GFP_ATOMIC); + } + + if (status & ISR_CSQ_W) { + fs_dprintk (FS_DEBUG_IRQ, "Command executed ok!\n"); + process_return_queue (dev, &dev->st_q); + } + + if (status & ISR_TBRQ_W) { + fs_dprintk (FS_DEBUG_IRQ, "Data transmitted!\n"); + process_txdone_queue (dev, &dev->tx_relq); + } + + func_exit (); + return IRQ_HANDLED; +} + + +#ifdef FS_POLL_FREQ +static void fs_poll (struct timer_list *t) +{ + struct fs_dev *dev = from_timer(dev, t, timer); + + fs_irq (0, dev); + dev->timer.expires = jiffies + FS_POLL_FREQ; + add_timer (&dev->timer); +} +#endif + +static int fs_init(struct fs_dev *dev) +{ + struct pci_dev *pci_dev; + int isr, to; + int i; + + func_enter (); + pci_dev = dev->pci_dev; + + printk (KERN_INFO "found a FireStream %d card, base %16llx, irq%d.\n", + IS_FS50(dev)?50:155, + (unsigned long long)pci_resource_start(pci_dev, 0), + dev->pci_dev->irq); + + if (fs_debug & FS_DEBUG_INIT) + my_hd ((unsigned char *) dev, sizeof (*dev)); + + undocumented_pci_fix (pci_dev); + + dev->hw_base = pci_resource_start(pci_dev, 0); + + dev->base = ioremap(dev->hw_base, 0x1000); + if (!dev->base) + return 1; + + reset_chip (dev); + + write_fs (dev, SARMODE0, 0 + | (0 * SARMODE0_SHADEN) /* We don't use shadow registers. */ + | (1 * SARMODE0_INTMODE_READCLEAR) + | (1 * SARMODE0_CWRE) + | (IS_FS50(dev) ? SARMODE0_PRPWT_FS50_5: + SARMODE0_PRPWT_FS155_3) + | (1 * SARMODE0_CALSUP_1) + | (IS_FS50(dev) ? (0 + | SARMODE0_RXVCS_32 + | SARMODE0_ABRVCS_32 + | SARMODE0_TXVCS_32): + (0 + | SARMODE0_RXVCS_1k + | SARMODE0_ABRVCS_1k + | SARMODE0_TXVCS_1k))); + + /* 10ms * 100 is 1 second. That should be enough, as AN3:9 says it takes + 1ms. */ + to = 100; + while (--to) { + isr = read_fs (dev, ISR); + + /* This bit is documented as "RESERVED" */ + if (isr & ISR_INIT_ERR) { + printk (KERN_ERR "Error initializing the FS... \n"); + goto unmap; + } + if (isr & ISR_INIT) { + fs_dprintk (FS_DEBUG_INIT, "Ha! Initialized OK!\n"); + break; + } + + /* Try again after 10ms. */ + msleep(10); + } + + if (!to) { + printk (KERN_ERR "timeout initializing the FS... \n"); + goto unmap; + } + + /* XXX fix for fs155 */ + dev->channel_mask = 0x1f; + dev->channo = 0; + + /* AN3: 10 */ + write_fs (dev, SARMODE1, 0 + | (fs_keystream * SARMODE1_DEFHEC) /* XXX PHY */ + | ((loopback == 1) * SARMODE1_TSTLP) /* XXX Loopback mode enable... */ + | (1 * SARMODE1_DCRM) + | (1 * SARMODE1_DCOAM) + | (0 * SARMODE1_OAMCRC) + | (0 * SARMODE1_DUMPE) + | (0 * SARMODE1_GPLEN) + | (0 * SARMODE1_GNAM) + | (0 * SARMODE1_GVAS) + | (0 * SARMODE1_GPAS) + | (1 * SARMODE1_GPRI) + | (0 * SARMODE1_PMS) + | (0 * SARMODE1_GFCR) + | (1 * SARMODE1_HECM2) + | (1 * SARMODE1_HECM1) + | (1 * SARMODE1_HECM0) + | (1 << 12) /* That's what hang's driver does. Program to 0 */ + | (0 * 0xff) /* XXX FS155 */); + + + /* Cal prescale etc */ + + /* AN3: 11 */ + write_fs (dev, TMCONF, 0x0000000f); + write_fs (dev, CALPRESCALE, 0x01010101 * num); + write_fs (dev, 0x80, 0x000F00E4); + + /* AN3: 12 */ + write_fs (dev, CELLOSCONF, 0 + | ( 0 * CELLOSCONF_CEN) + | ( CELLOSCONF_SC1) + | (0x80 * CELLOSCONF_COBS) + | (num * CELLOSCONF_COPK) /* Changed from 0xff to 0x5a */ + | (num * CELLOSCONF_COST));/* after a hint from Hang. + * performance jumped 50->70... */ + + /* Magic value by Hang */ + write_fs (dev, CELLOSCONF_COST, 0x0B809191); + + if (IS_FS50 (dev)) { + write_fs (dev, RAS0, RAS0_DCD_XHLT); + dev->atm_dev->ci_range.vpi_bits = 12; + dev->atm_dev->ci_range.vci_bits = 16; + dev->nchannels = FS50_NR_CHANNELS; + } else { + write_fs (dev, RAS0, RAS0_DCD_XHLT + | (((1 << FS155_VPI_BITS) - 1) * RAS0_VPSEL) + | (((1 << FS155_VCI_BITS) - 1) * RAS0_VCSEL)); + /* We can chose the split arbitrarily. We might be able to + support more. Whatever. This should do for now. */ + dev->atm_dev->ci_range.vpi_bits = FS155_VPI_BITS; + dev->atm_dev->ci_range.vci_bits = FS155_VCI_BITS; + + /* Address bits we can't use should be compared to 0. */ + write_fs (dev, RAC, 0); + + /* Manual (AN9, page 6) says ASF1=0 means compare Utopia address + * too. I can't find ASF1 anywhere. Anyway, we AND with just the + * other bits, then compare with 0, which is exactly what we + * want. */ + write_fs (dev, RAM, (1 << (28 - FS155_VPI_BITS - FS155_VCI_BITS)) - 1); + dev->nchannels = FS155_NR_CHANNELS; + } + dev->atm_vccs = kcalloc (dev->nchannels, sizeof (struct atm_vcc *), + GFP_KERNEL); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc atmvccs: %p(%zd)\n", + dev->atm_vccs, dev->nchannels * sizeof (struct atm_vcc *)); + + if (!dev->atm_vccs) { + printk (KERN_WARNING "Couldn't allocate memory for VCC buffers. Woops!\n"); + /* XXX Clean up..... */ + goto unmap; + } + + dev->tx_inuse = kzalloc (dev->nchannels / 8 /* bits/byte */ , GFP_KERNEL); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc tx_inuse: %p(%d)\n", + dev->atm_vccs, dev->nchannels / 8); + + if (!dev->tx_inuse) { + printk (KERN_WARNING "Couldn't allocate memory for tx_inuse bits!\n"); + /* XXX Clean up..... */ + goto unmap; + } + /* -- RAS1 : FS155 and 50 differ. Default (0) should be OK for both */ + /* -- RAS2 : FS50 only: Default is OK. */ + + /* DMAMODE, default should be OK. -- REW */ + write_fs (dev, DMAMR, DMAMR_TX_MODE_FULL); + + init_q (dev, &dev->hp_txq, TX_PQ(TXQ_HP), TXQ_NENTRIES, 0); + init_q (dev, &dev->lp_txq, TX_PQ(TXQ_LP), TXQ_NENTRIES, 0); + init_q (dev, &dev->tx_relq, TXB_RQ, TXQ_NENTRIES, 1); + init_q (dev, &dev->st_q, ST_Q, TXQ_NENTRIES, 1); + + for (i=0;i < FS_NR_FREE_POOLS;i++) { + init_fp (dev, &dev->rx_fp[i], RXB_FP(i), + rx_buf_sizes[i], rx_pool_sizes[i]); + top_off_fp (dev, &dev->rx_fp[i], GFP_KERNEL); + } + + + for (i=0;i < FS_NR_RX_QUEUES;i++) + init_q (dev, &dev->rx_rq[i], RXB_RQ(i), RXRQ_NENTRIES, 1); + + dev->irq = pci_dev->irq; + if (request_irq (dev->irq, fs_irq, IRQF_SHARED, "firestream", dev)) { + printk (KERN_WARNING "couldn't get irq %d for firestream.\n", pci_dev->irq); + /* XXX undo all previous stuff... */ + goto unmap; + } + fs_dprintk (FS_DEBUG_INIT, "Grabbed irq %d for dev at %p.\n", dev->irq, dev); + + /* We want to be notified of most things. Just the statistics count + overflows are not interesting */ + write_fs (dev, IMR, 0 + | ISR_RBRQ0_W + | ISR_RBRQ1_W + | ISR_RBRQ2_W + | ISR_RBRQ3_W + | ISR_TBRQ_W + | ISR_CSQ_W); + + write_fs (dev, SARMODE0, 0 + | (0 * SARMODE0_SHADEN) /* We don't use shadow registers. */ + | (1 * SARMODE0_GINT) + | (1 * SARMODE0_INTMODE_READCLEAR) + | (0 * SARMODE0_CWRE) + | (IS_FS50(dev)?SARMODE0_PRPWT_FS50_5: + SARMODE0_PRPWT_FS155_3) + | (1 * SARMODE0_CALSUP_1) + | (IS_FS50 (dev)?(0 + | SARMODE0_RXVCS_32 + | SARMODE0_ABRVCS_32 + | SARMODE0_TXVCS_32): + (0 + | SARMODE0_RXVCS_1k + | SARMODE0_ABRVCS_1k + | SARMODE0_TXVCS_1k)) + | (1 * SARMODE0_RUN)); + + init_phy (dev, PHY_NTC_INIT); + + if (loopback == 2) { + write_phy (dev, 0x39, 0x000e); + } + +#ifdef FS_POLL_FREQ + timer_setup(&dev->timer, fs_poll, 0); + dev->timer.expires = jiffies + FS_POLL_FREQ; + add_timer (&dev->timer); +#endif + + dev->atm_dev->dev_data = dev; + + func_exit (); + return 0; +unmap: + iounmap(dev->base); + return 1; +} + +static int firestream_init_one(struct pci_dev *pci_dev, + const struct pci_device_id *ent) +{ + struct atm_dev *atm_dev; + struct fs_dev *fs_dev; + + if (pci_enable_device(pci_dev)) + goto err_out; + + fs_dev = kzalloc (sizeof (struct fs_dev), GFP_KERNEL); + fs_dprintk (FS_DEBUG_ALLOC, "Alloc fs-dev: %p(%zd)\n", + fs_dev, sizeof (struct fs_dev)); + if (!fs_dev) + goto err_out; + atm_dev = atm_dev_register("fs", &pci_dev->dev, &ops, -1, NULL); + if (!atm_dev) + goto err_out_free_fs_dev; + + fs_dev->pci_dev = pci_dev; + fs_dev->atm_dev = atm_dev; + fs_dev->flags = ent->driver_data; + + if (fs_init(fs_dev)) + goto err_out_free_atm_dev; + + fs_dev->next = fs_boards; + fs_boards = fs_dev; + return 0; + + err_out_free_atm_dev: + atm_dev_deregister(atm_dev); + err_out_free_fs_dev: + kfree(fs_dev); + err_out: + return -ENODEV; +} + +static void firestream_remove_one(struct pci_dev *pdev) +{ + int i; + struct fs_dev *dev, *nxtdev; + struct fs_vcc *vcc; + struct FS_BPENTRY *fp, *nxt; + + func_enter (); + +#if 0 + printk ("hptxq:\n"); + for (i=0;i<60;i++) { + printk ("%d: %08x %08x %08x %08x \n", + i, pq[qp].cmd, pq[qp].p0, pq[qp].p1, pq[qp].p2); + qp++; + if (qp >= 60) qp = 0; + } + + printk ("descriptors:\n"); + for (i=0;i<60;i++) { + printk ("%d: %p: %08x %08x %p %p\n", + i, da[qd], dq[qd].flags, dq[qd].bsa, dq[qd].skb, dq[qd].dev); + qd++; + if (qd >= 60) qd = 0; + } +#endif + + for (dev = fs_boards;dev != NULL;dev=nxtdev) { + fs_dprintk (FS_DEBUG_CLEANUP, "Releasing resources for dev at %p.\n", dev); + + /* XXX Hit all the tx channels too! */ + + for (i=0;i < dev->nchannels;i++) { + if (dev->atm_vccs[i]) { + vcc = FS_VCC (dev->atm_vccs[i]); + submit_command (dev, &dev->hp_txq, + QE_CMD_TX_PURGE_INH | QE_CMD_IMM_INQ | vcc->channo, 0,0,0); + submit_command (dev, &dev->hp_txq, + QE_CMD_RX_PURGE_INH | QE_CMD_IMM_INQ | vcc->channo, 0,0,0); + + } + } + + /* XXX Wait a while for the chip to release all buffers. */ + + for (i=0;i < FS_NR_FREE_POOLS;i++) { + for (fp=bus_to_virt (read_fs (dev, FP_SA(dev->rx_fp[i].offset))); + !(fp->flags & FP_FLAGS_EPI);fp = nxt) { + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", fp->skb); + dev_kfree_skb_any (fp->skb); + nxt = bus_to_virt (fp->next); + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", fp); + kfree (fp); + } + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-skb: %p\n", fp->skb); + dev_kfree_skb_any (fp->skb); + fs_dprintk (FS_DEBUG_ALLOC, "Free rec-d: %p\n", fp); + kfree (fp); + } + + /* Hang the chip in "reset", prevent it clobbering memory that is + no longer ours. */ + reset_chip (dev); + + fs_dprintk (FS_DEBUG_CLEANUP, "Freeing irq%d.\n", dev->irq); + free_irq (dev->irq, dev); + del_timer_sync (&dev->timer); + + atm_dev_deregister(dev->atm_dev); + free_queue (dev, &dev->hp_txq); + free_queue (dev, &dev->lp_txq); + free_queue (dev, &dev->tx_relq); + free_queue (dev, &dev->st_q); + + fs_dprintk (FS_DEBUG_ALLOC, "Free atmvccs: %p\n", dev->atm_vccs); + kfree (dev->atm_vccs); + + for (i=0;i< FS_NR_FREE_POOLS;i++) + free_freepool (dev, &dev->rx_fp[i]); + + for (i=0;i < FS_NR_RX_QUEUES;i++) + free_queue (dev, &dev->rx_rq[i]); + + iounmap(dev->base); + fs_dprintk (FS_DEBUG_ALLOC, "Free fs-dev: %p\n", dev); + nxtdev = dev->next; + kfree (dev); + } + + func_exit (); +} + +static const struct pci_device_id firestream_pci_tbl[] = { + { PCI_VDEVICE(FUJITSU_ME, PCI_DEVICE_ID_FUJITSU_FS50), FS_IS50}, + { PCI_VDEVICE(FUJITSU_ME, PCI_DEVICE_ID_FUJITSU_FS155), FS_IS155}, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, firestream_pci_tbl); + +static struct pci_driver firestream_driver = { + .name = "firestream", + .id_table = firestream_pci_tbl, + .probe = firestream_init_one, + .remove = firestream_remove_one, +}; + +static int __init firestream_init_module (void) +{ + int error; + + func_enter (); + error = pci_register_driver(&firestream_driver); + func_exit (); + return error; +} + +static void __exit firestream_cleanup_module(void) +{ + pci_unregister_driver(&firestream_driver); +} + +module_init(firestream_init_module); +module_exit(firestream_cleanup_module); + +MODULE_LICENSE("GPL"); + + + diff --git a/drivers/atm/firestream.h b/drivers/atm/firestream.h new file mode 100644 index 000000000..6d6841608 --- /dev/null +++ b/drivers/atm/firestream.h @@ -0,0 +1,502 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* drivers/atm/firestream.h - FireStream 155 (MB86697) and + * FireStream 50 (MB86695) device driver + */ + +/* Written & (C) 2000 by R.E.Wolff@BitWizard.nl + * Copied snippets from zatm.c by Werner Almesberger, EPFL LRC/ICA + * and ambassador.c Copyright (C) 1995-1999 Madge Networks Ltd + */ + +/* +*/ + + +/*********************************************************************** + * first the defines for the chip. * + ***********************************************************************/ + + +/********************* General chip parameters. ************************/ + +#define FS_NR_FREE_POOLS 8 +#define FS_NR_RX_QUEUES 4 + + +/********************* queues and queue access macros ******************/ + + +/* A queue entry. */ +struct FS_QENTRY { + u32 cmd; + u32 p0, p1, p2; +}; + + +/* A freepool entry. */ +struct FS_BPENTRY { + u32 flags; + u32 next; + u32 bsa; + u32 aal_bufsize; + + /* The hardware doesn't look at this, but we need the SKB somewhere... */ + struct sk_buff *skb; + struct freepool *fp; + struct fs_dev *dev; +}; + + +#define STATUS_CODE(qe) ((qe->cmd >> 22) & 0x3f) + + +/* OFFSETS against the base of a QUEUE... */ +#define QSA 0x00 +#define QEA 0x04 +#define QRP 0x08 +#define QWP 0x0c +#define QCNF 0x10 /* Only for Release queues! */ +/* Not for the transmit pending queue. */ + + +/* OFFSETS against the base of a FREE POOL... */ +#define FPCNF 0x00 +#define FPSA 0x04 +#define FPEA 0x08 +#define FPCNT 0x0c +#define FPCTU 0x10 + +#define Q_SA(b) (b + QSA ) +#define Q_EA(b) (b + QEA ) +#define Q_RP(b) (b + QRP ) +#define Q_WP(b) (b + QWP ) +#define Q_CNF(b) (b + QCNF) + +#define FP_CNF(b) (b + FPCNF) +#define FP_SA(b) (b + FPSA) +#define FP_EA(b) (b + FPEA) +#define FP_CNT(b) (b + FPCNT) +#define FP_CTU(b) (b + FPCTU) + +/* bits in a queue register. */ +#define Q_FULL 0x1 +#define Q_EMPTY 0x2 +#define Q_INCWRAP 0x4 +#define Q_ADDR_MASK 0xfffffff0 + +/* bits in a FreePool config register */ +#define RBFP_RBS (0x1 << 16) +#define RBFP_RBSVAL (0x1 << 15) +#define RBFP_CME (0x1 << 12) +#define RBFP_DLP (0x1 << 11) +#define RBFP_BFPWT (0x1 << 0) + + + + +/* FireStream commands. */ +#define QE_CMD_NULL (0x00 << 22) +#define QE_CMD_REG_RD (0x01 << 22) +#define QE_CMD_REG_RDM (0x02 << 22) +#define QE_CMD_REG_WR (0x03 << 22) +#define QE_CMD_REG_WRM (0x04 << 22) +#define QE_CMD_CONFIG_TX (0x05 << 22) +#define QE_CMD_CONFIG_RX (0x06 << 22) +#define QE_CMD_PRP_RD (0x07 << 22) +#define QE_CMD_PRP_RDM (0x2a << 22) +#define QE_CMD_PRP_WR (0x09 << 22) +#define QE_CMD_PRP_WRM (0x2b << 22) +#define QE_CMD_RX_EN (0x0a << 22) +#define QE_CMD_RX_PURGE (0x0b << 22) +#define QE_CMD_RX_PURGE_INH (0x0c << 22) +#define QE_CMD_TX_EN (0x0d << 22) +#define QE_CMD_TX_PURGE (0x0e << 22) +#define QE_CMD_TX_PURGE_INH (0x0f << 22) +#define QE_CMD_RST_CG (0x10 << 22) +#define QE_CMD_SET_CG (0x11 << 22) +#define QE_CMD_RST_CLP (0x12 << 22) +#define QE_CMD_SET_CLP (0x13 << 22) +#define QE_CMD_OVERRIDE (0x14 << 22) +#define QE_CMD_ADD_BFP (0x15 << 22) +#define QE_CMD_DUMP_TX (0x16 << 22) +#define QE_CMD_DUMP_RX (0x17 << 22) +#define QE_CMD_LRAM_RD (0x18 << 22) +#define QE_CMD_LRAM_RDM (0x28 << 22) +#define QE_CMD_LRAM_WR (0x19 << 22) +#define QE_CMD_LRAM_WRM (0x29 << 22) +#define QE_CMD_LRAM_BSET (0x1a << 22) +#define QE_CMD_LRAM_BCLR (0x1b << 22) +#define QE_CMD_CONFIG_SEGM (0x1c << 22) +#define QE_CMD_READ_SEGM (0x1d << 22) +#define QE_CMD_CONFIG_ROUT (0x1e << 22) +#define QE_CMD_READ_ROUT (0x1f << 22) +#define QE_CMD_CONFIG_TM (0x20 << 22) +#define QE_CMD_READ_TM (0x21 << 22) +#define QE_CMD_CONFIG_TXBM (0x22 << 22) +#define QE_CMD_READ_TXBM (0x23 << 22) +#define QE_CMD_CONFIG_RXBM (0x24 << 22) +#define QE_CMD_READ_RXBM (0x25 << 22) +#define QE_CMD_CONFIG_REAS (0x26 << 22) +#define QE_CMD_READ_REAS (0x27 << 22) + +#define QE_TRANSMIT_DE (0x0 << 30) +#define QE_CMD_LINKED (0x1 << 30) +#define QE_CMD_IMM (0x2 << 30) +#define QE_CMD_IMM_INQ (0x3 << 30) + +#define TD_EPI (0x1 << 27) +#define TD_COMMAND (0x1 << 28) + +#define TD_DATA (0x0 << 29) +#define TD_RM_CELL (0x1 << 29) +#define TD_OAM_CELL (0x2 << 29) +#define TD_OAM_CELL_SEGMENT (0x3 << 29) + +#define TD_BPI (0x1 << 20) + +#define FP_FLAGS_EPI (0x1 << 27) + + +#define TX_PQ(i) (0x00 + (i) * 0x10) +#define TXB_RQ (0x20) +#define ST_Q (0x48) +#define RXB_FP(i) (0x90 + (i) * 0x14) +#define RXB_RQ(i) (0x134 + (i) * 0x14) + + +#define TXQ_HP 0 +#define TXQ_LP 1 + +/* Phew. You don't want to know how many revisions these simple queue + * address macros went through before I got them nice and compact as + * they are now. -- REW + */ + + +/* And now for something completely different: + * The rest of the registers... */ + + +#define CMDR0 0x34 +#define CMDR1 0x38 +#define CMDR2 0x3c +#define CMDR3 0x40 + + +#define SARMODE0 0x5c + +#define SARMODE0_TXVCS_0 (0x0 << 0) +#define SARMODE0_TXVCS_1k (0x1 << 0) +#define SARMODE0_TXVCS_2k (0x2 << 0) +#define SARMODE0_TXVCS_4k (0x3 << 0) +#define SARMODE0_TXVCS_8k (0x4 << 0) +#define SARMODE0_TXVCS_16k (0x5 << 0) +#define SARMODE0_TXVCS_32k (0x6 << 0) +#define SARMODE0_TXVCS_64k (0x7 << 0) +#define SARMODE0_TXVCS_32 (0x8 << 0) + +#define SARMODE0_ABRVCS_0 (0x0 << 4) +#define SARMODE0_ABRVCS_512 (0x1 << 4) +#define SARMODE0_ABRVCS_1k (0x2 << 4) +#define SARMODE0_ABRVCS_2k (0x3 << 4) +#define SARMODE0_ABRVCS_4k (0x4 << 4) +#define SARMODE0_ABRVCS_8k (0x5 << 4) +#define SARMODE0_ABRVCS_16k (0x6 << 4) +#define SARMODE0_ABRVCS_32k (0x7 << 4) +#define SARMODE0_ABRVCS_32 (0x9 << 4) /* The others are "8", this one really has to + be 9. Tell me you don't believe me. -- REW */ + +#define SARMODE0_RXVCS_0 (0x0 << 8) +#define SARMODE0_RXVCS_1k (0x1 << 8) +#define SARMODE0_RXVCS_2k (0x2 << 8) +#define SARMODE0_RXVCS_4k (0x3 << 8) +#define SARMODE0_RXVCS_8k (0x4 << 8) +#define SARMODE0_RXVCS_16k (0x5 << 8) +#define SARMODE0_RXVCS_32k (0x6 << 8) +#define SARMODE0_RXVCS_64k (0x7 << 8) +#define SARMODE0_RXVCS_32 (0x8 << 8) + +#define SARMODE0_CALSUP_1 (0x0 << 12) +#define SARMODE0_CALSUP_2 (0x1 << 12) +#define SARMODE0_CALSUP_3 (0x2 << 12) +#define SARMODE0_CALSUP_4 (0x3 << 12) + +#define SARMODE0_PRPWT_FS50_0 (0x0 << 14) +#define SARMODE0_PRPWT_FS50_2 (0x1 << 14) +#define SARMODE0_PRPWT_FS50_5 (0x2 << 14) +#define SARMODE0_PRPWT_FS50_11 (0x3 << 14) + +#define SARMODE0_PRPWT_FS155_0 (0x0 << 14) +#define SARMODE0_PRPWT_FS155_1 (0x1 << 14) +#define SARMODE0_PRPWT_FS155_2 (0x2 << 14) +#define SARMODE0_PRPWT_FS155_3 (0x3 << 14) + +#define SARMODE0_SRTS0 (0x1 << 23) +#define SARMODE0_SRTS1 (0x1 << 24) + +#define SARMODE0_RUN (0x1 << 25) + +#define SARMODE0_UNLOCK (0x1 << 26) +#define SARMODE0_CWRE (0x1 << 27) + + +#define SARMODE0_INTMODE_READCLEAR (0x0 << 28) +#define SARMODE0_INTMODE_READNOCLEAR (0x1 << 28) +#define SARMODE0_INTMODE_READNOCLEARINHIBIT (0x2 << 28) +#define SARMODE0_INTMODE_READCLEARINHIBIT (0x3 << 28) /* Tell me you don't believe me. */ + +#define SARMODE0_GINT (0x1 << 30) +#define SARMODE0_SHADEN (0x1 << 31) + + +#define SARMODE1 0x60 + + +#define SARMODE1_TRTL_SHIFT 0 /* Program to 0 */ +#define SARMODE1_RRTL_SHIFT 4 /* Program to 0 */ + +#define SARMODE1_TAGM (0x1 << 8) /* Program to 0 */ + +#define SARMODE1_HECM0 (0x1 << 9) +#define SARMODE1_HECM1 (0x1 << 10) +#define SARMODE1_HECM2 (0x1 << 11) + +#define SARMODE1_GFCE (0x1 << 14) +#define SARMODE1_GFCR (0x1 << 15) +#define SARMODE1_PMS (0x1 << 18) +#define SARMODE1_GPRI (0x1 << 19) +#define SARMODE1_GPAS (0x1 << 20) +#define SARMODE1_GVAS (0x1 << 21) +#define SARMODE1_GNAM (0x1 << 22) +#define SARMODE1_GPLEN (0x1 << 23) +#define SARMODE1_DUMPE (0x1 << 24) +#define SARMODE1_OAMCRC (0x1 << 25) +#define SARMODE1_DCOAM (0x1 << 26) +#define SARMODE1_DCRM (0x1 << 27) +#define SARMODE1_TSTLP (0x1 << 28) +#define SARMODE1_DEFHEC (0x1 << 29) + + +#define ISR 0x64 +#define IUSR 0x68 +#define IMR 0x6c + +#define ISR_LPCO (0x1 << 0) +#define ISR_DPCO (0x1 << 1) +#define ISR_RBRQ0_W (0x1 << 2) +#define ISR_RBRQ1_W (0x1 << 3) +#define ISR_RBRQ2_W (0x1 << 4) +#define ISR_RBRQ3_W (0x1 << 5) +#define ISR_RBRQ0_NF (0x1 << 6) +#define ISR_RBRQ1_NF (0x1 << 7) +#define ISR_RBRQ2_NF (0x1 << 8) +#define ISR_RBRQ3_NF (0x1 << 9) +#define ISR_BFP_SC (0x1 << 10) +#define ISR_INIT (0x1 << 11) +#define ISR_INIT_ERR (0x1 << 12) /* Documented as "reserved" */ +#define ISR_USCEO (0x1 << 13) +#define ISR_UPEC0 (0x1 << 14) +#define ISR_VPFCO (0x1 << 15) +#define ISR_CRCCO (0x1 << 16) +#define ISR_HECO (0x1 << 17) +#define ISR_TBRQ_W (0x1 << 18) +#define ISR_TBRQ_NF (0x1 << 19) +#define ISR_CTPQ_E (0x1 << 20) +#define ISR_GFC_C0 (0x1 << 21) +#define ISR_PCI_FTL (0x1 << 22) +#define ISR_CSQ_W (0x1 << 23) +#define ISR_CSQ_NF (0x1 << 24) +#define ISR_EXT_INT (0x1 << 25) +#define ISR_RXDMA_S (0x1 << 26) + + +#define TMCONF 0x78 +/* Bits? */ + + +#define CALPRESCALE 0x7c +/* Bits? */ + +#define CELLOSCONF 0x84 +#define CELLOSCONF_COTS (0x1 << 28) +#define CELLOSCONF_CEN (0x1 << 27) +#define CELLOSCONF_SC8 (0x3 << 24) +#define CELLOSCONF_SC4 (0x2 << 24) +#define CELLOSCONF_SC2 (0x1 << 24) +#define CELLOSCONF_SC1 (0x0 << 24) + +#define CELLOSCONF_COBS (0x1 << 16) +#define CELLOSCONF_COPK (0x1 << 8) +#define CELLOSCONF_COST (0x1 << 0) +/* Bits? */ + +#define RAS0 0x1bc +#define RAS0_DCD_XHLT (0x1 << 31) + +#define RAS0_VPSEL (0x1 << 16) +#define RAS0_VCSEL (0x1 << 0) + +#define RAS1 0x1c0 +#define RAS1_UTREG (0x1 << 5) + + +#define DMAMR 0x1cc +#define DMAMR_TX_MODE_FULL (0x0 << 0) +#define DMAMR_TX_MODE_PART (0x1 << 0) +#define DMAMR_TX_MODE_NONE (0x2 << 0) /* And 3 */ + + + +#define RAS2 0x280 + +#define RAS2_NNI (0x1 << 0) +#define RAS2_USEL (0x1 << 1) +#define RAS2_UBS (0x1 << 2) + + + +struct fs_transmit_config { + u32 flags; + u32 atm_hdr; + u32 TMC[4]; + u32 spec; + u32 rtag[3]; +}; + +#define TC_FLAGS_AAL5 (0x0 << 29) +#define TC_FLAGS_TRANSPARENT_PAYLOAD (0x1 << 29) +#define TC_FLAGS_TRANSPARENT_CELL (0x2 << 29) +#define TC_FLAGS_STREAMING (0x1 << 28) +#define TC_FLAGS_PACKET (0x0) +#define TC_FLAGS_TYPE_ABR (0x0 << 22) +#define TC_FLAGS_TYPE_CBR (0x1 << 22) +#define TC_FLAGS_TYPE_VBR (0x2 << 22) +#define TC_FLAGS_TYPE_UBR (0x3 << 22) +#define TC_FLAGS_CAL0 (0x0 << 20) +#define TC_FLAGS_CAL1 (0x1 << 20) +#define TC_FLAGS_CAL2 (0x2 << 20) +#define TC_FLAGS_CAL3 (0x3 << 20) + + +#define RC_FLAGS_NAM (0x1 << 13) +#define RC_FLAGS_RXBM_PSB (0x0 << 14) +#define RC_FLAGS_RXBM_CIF (0x1 << 14) +#define RC_FLAGS_RXBM_PMB (0x2 << 14) +#define RC_FLAGS_RXBM_STR (0x4 << 14) +#define RC_FLAGS_RXBM_SAF (0x6 << 14) +#define RC_FLAGS_RXBM_POS (0x6 << 14) +#define RC_FLAGS_BFPS (0x1 << 17) + +#define RC_FLAGS_BFPS_BFP (0x1 << 17) + +#define RC_FLAGS_BFPS_BFP0 (0x0 << 17) +#define RC_FLAGS_BFPS_BFP1 (0x1 << 17) +#define RC_FLAGS_BFPS_BFP2 (0x2 << 17) +#define RC_FLAGS_BFPS_BFP3 (0x3 << 17) +#define RC_FLAGS_BFPS_BFP4 (0x4 << 17) +#define RC_FLAGS_BFPS_BFP5 (0x5 << 17) +#define RC_FLAGS_BFPS_BFP6 (0x6 << 17) +#define RC_FLAGS_BFPS_BFP7 (0x7 << 17) +#define RC_FLAGS_BFPS_BFP01 (0x8 << 17) +#define RC_FLAGS_BFPS_BFP23 (0x9 << 17) +#define RC_FLAGS_BFPS_BFP45 (0xa << 17) +#define RC_FLAGS_BFPS_BFP67 (0xb << 17) +#define RC_FLAGS_BFPS_BFP07 (0xc << 17) +#define RC_FLAGS_BFPS_BFP27 (0xd << 17) +#define RC_FLAGS_BFPS_BFP47 (0xe << 17) + +#define RC_FLAGS_BFPP (0x1 << 21) +#define RC_FLAGS_TEVC (0x1 << 22) +#define RC_FLAGS_TEP (0x1 << 23) +#define RC_FLAGS_AAL5 (0x0 << 24) +#define RC_FLAGS_TRANSP (0x1 << 24) +#define RC_FLAGS_TRANSC (0x2 << 24) +#define RC_FLAGS_ML (0x1 << 27) +#define RC_FLAGS_TRBRM (0x1 << 28) +#define RC_FLAGS_PRI (0x1 << 29) +#define RC_FLAGS_HOAM (0x1 << 30) +#define RC_FLAGS_CRC10 (0x1 << 31) + + +#define RAC 0x1c8 +#define RAM 0x1c4 + + + +/************************************************************************ + * Then the datastructures that the DRIVER uses. * + ************************************************************************/ + +#define TXQ_NENTRIES 32 +#define RXRQ_NENTRIES 1024 + + +struct fs_vcc { + int channo; + wait_queue_head_t close_wait; + struct sk_buff *last_skb; +}; + + +struct queue { + struct FS_QENTRY *sa, *ea; + int offset; +}; + +struct freepool { + int offset; + int bufsize; + int nr_buffers; + int n; +}; + + +struct fs_dev { + struct fs_dev *next; /* other FS devices */ + int flags; + + unsigned char irq; /* IRQ */ + struct pci_dev *pci_dev; /* PCI stuff */ + struct atm_dev *atm_dev; + struct timer_list timer; + + unsigned long hw_base; /* mem base address */ + void __iomem *base; /* Mapping of base address */ + int channo; + unsigned long channel_mask; + + struct queue hp_txq, lp_txq, tx_relq, st_q; + struct freepool rx_fp[FS_NR_FREE_POOLS]; + struct queue rx_rq[FS_NR_RX_QUEUES]; + + int nchannels; + struct atm_vcc **atm_vccs; + void *tx_inuse; + int ntxpckts; +}; + + + + +/* Number of channesl that the FS50 supports. */ +#define FS50_CHANNEL_BITS 5 +#define FS50_NR_CHANNELS (1 << FS50_CHANNEL_BITS) + + +#define FS_DEV(atm_dev) ((struct fs_dev *) (atm_dev)->dev_data) +#define FS_VCC(atm_vcc) ((struct fs_vcc *) (atm_vcc)->dev_data) + + +#define FS_IS50 0x1 +#define FS_IS155 0x2 + +#define IS_FS50(dev) (dev->flags & FS_IS50) +#define IS_FS155(dev) (dev->flags & FS_IS155) + +/* Within limits this is user-configurable. */ +/* Note: Currently the sum (10 -> 1k channels) is hardcoded in the driver. */ +#define FS155_VPI_BITS 4 +#define FS155_VCI_BITS 6 + +#define FS155_CHANNEL_BITS (FS155_VPI_BITS + FS155_VCI_BITS) +#define FS155_NR_CHANNELS (1 << FS155_CHANNEL_BITS) diff --git a/drivers/atm/fore200e.c b/drivers/atm/fore200e.c new file mode 100644 index 000000000..9a70bee84 --- /dev/null +++ b/drivers/atm/fore200e.c @@ -0,0 +1,3020 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + A FORE Systems 200E-series driver for ATM on Linux. + Christophe Lizzi (lizzi@cnam.fr), October 1999-March 2003. + + Based on the PCA-200E driver from Uwe Dannowski (Uwe.Dannowski@inf.tu-dresden.de). + + This driver simultaneously supports PCA-200E and SBA-200E adapters + on i386, alpha (untested), powerpc, sparc and sparc64 architectures. + +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SBUS +#include +#include +#include +#include +#include +#endif + +#if defined(CONFIG_ATM_FORE200E_USE_TASKLET) /* defer interrupt work to a tasklet */ +#define FORE200E_USE_TASKLET +#endif + +#if 0 /* enable the debugging code of the buffer supply queues */ +#define FORE200E_BSQ_DEBUG +#endif + +#if 1 /* ensure correct handling of 52-byte AAL0 SDUs expected by atmdump-like apps */ +#define FORE200E_52BYTE_AAL0_SDU +#endif + +#include "fore200e.h" +#include "suni.h" + +#define FORE200E_VERSION "0.3e" + +#define FORE200E "fore200e: " + +#if 0 /* override .config */ +#define CONFIG_ATM_FORE200E_DEBUG 1 +#endif +#if defined(CONFIG_ATM_FORE200E_DEBUG) && (CONFIG_ATM_FORE200E_DEBUG > 0) +#define DPRINTK(level, format, args...) do { if (CONFIG_ATM_FORE200E_DEBUG >= (level)) \ + printk(FORE200E format, ##args); } while (0) +#else +#define DPRINTK(level, format, args...) do {} while (0) +#endif + + +#define FORE200E_ALIGN(addr, alignment) \ + ((((unsigned long)(addr) + (alignment - 1)) & ~(alignment - 1)) - (unsigned long)(addr)) + +#define FORE200E_DMA_INDEX(dma_addr, type, index) ((dma_addr) + (index) * sizeof(type)) + +#define FORE200E_INDEX(virt_addr, type, index) (&((type *)(virt_addr))[ index ]) + +#define FORE200E_NEXT_ENTRY(index, modulo) (index = ((index) + 1) % (modulo)) + +#if 1 +#define ASSERT(expr) if (!(expr)) { \ + printk(FORE200E "assertion failed! %s[%d]: %s\n", \ + __func__, __LINE__, #expr); \ + panic(FORE200E "%s", __func__); \ + } +#else +#define ASSERT(expr) do {} while (0) +#endif + + +static const struct atmdev_ops fore200e_ops; + +static LIST_HEAD(fore200e_boards); + + +MODULE_AUTHOR("Christophe Lizzi - credits to Uwe Dannowski and Heikki Vatiainen"); +MODULE_DESCRIPTION("FORE Systems 200E-series ATM driver - version " FORE200E_VERSION); +MODULE_SUPPORTED_DEVICE("PCA-200E, SBA-200E"); + + +static const int fore200e_rx_buf_nbr[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ] = { + { BUFFER_S1_NBR, BUFFER_L1_NBR }, + { BUFFER_S2_NBR, BUFFER_L2_NBR } +}; + +static const int fore200e_rx_buf_size[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ] = { + { BUFFER_S1_SIZE, BUFFER_L1_SIZE }, + { BUFFER_S2_SIZE, BUFFER_L2_SIZE } +}; + + +#if defined(CONFIG_ATM_FORE200E_DEBUG) && (CONFIG_ATM_FORE200E_DEBUG > 0) +static const char* fore200e_traffic_class[] = { "NONE", "UBR", "CBR", "VBR", "ABR", "ANY" }; +#endif + + +#if 0 /* currently unused */ +static int +fore200e_fore2atm_aal(enum fore200e_aal aal) +{ + switch(aal) { + case FORE200E_AAL0: return ATM_AAL0; + case FORE200E_AAL34: return ATM_AAL34; + case FORE200E_AAL5: return ATM_AAL5; + } + + return -EINVAL; +} +#endif + + +static enum fore200e_aal +fore200e_atm2fore_aal(int aal) +{ + switch(aal) { + case ATM_AAL0: return FORE200E_AAL0; + case ATM_AAL34: return FORE200E_AAL34; + case ATM_AAL1: + case ATM_AAL2: + case ATM_AAL5: return FORE200E_AAL5; + } + + return -EINVAL; +} + + +static char* +fore200e_irq_itoa(int irq) +{ + static char str[8]; + sprintf(str, "%d", irq); + return str; +} + + +/* allocate and align a chunk of memory intended to hold the data behing exchanged + between the driver and the adapter (using streaming DVMA) */ + +static int +fore200e_chunk_alloc(struct fore200e* fore200e, struct chunk* chunk, int size, int alignment, int direction) +{ + unsigned long offset = 0; + + if (alignment <= sizeof(int)) + alignment = 0; + + chunk->alloc_size = size + alignment; + chunk->direction = direction; + + chunk->alloc_addr = kzalloc(chunk->alloc_size, GFP_KERNEL); + if (chunk->alloc_addr == NULL) + return -ENOMEM; + + if (alignment > 0) + offset = FORE200E_ALIGN(chunk->alloc_addr, alignment); + + chunk->align_addr = chunk->alloc_addr + offset; + + chunk->dma_addr = dma_map_single(fore200e->dev, chunk->align_addr, + size, direction); + if (dma_mapping_error(fore200e->dev, chunk->dma_addr)) { + kfree(chunk->alloc_addr); + return -ENOMEM; + } + return 0; +} + + +/* free a chunk of memory */ + +static void +fore200e_chunk_free(struct fore200e* fore200e, struct chunk* chunk) +{ + dma_unmap_single(fore200e->dev, chunk->dma_addr, chunk->dma_size, + chunk->direction); + kfree(chunk->alloc_addr); +} + +/* + * Allocate a DMA consistent chunk of memory intended to act as a communication + * mechanism (to hold descriptors, status, queues, etc.) shared by the driver + * and the adapter. + */ +static int +fore200e_dma_chunk_alloc(struct fore200e *fore200e, struct chunk *chunk, + int size, int nbr, int alignment) +{ + /* returned chunks are page-aligned */ + chunk->alloc_size = size * nbr; + chunk->alloc_addr = dma_alloc_coherent(fore200e->dev, chunk->alloc_size, + &chunk->dma_addr, GFP_KERNEL); + if (!chunk->alloc_addr) + return -ENOMEM; + chunk->align_addr = chunk->alloc_addr; + return 0; +} + +/* + * Free a DMA consistent chunk of memory. + */ +static void +fore200e_dma_chunk_free(struct fore200e* fore200e, struct chunk* chunk) +{ + dma_free_coherent(fore200e->dev, chunk->alloc_size, chunk->alloc_addr, + chunk->dma_addr); +} + +static void +fore200e_spin(int msecs) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(msecs); + while (time_before(jiffies, timeout)); +} + + +static int +fore200e_poll(struct fore200e* fore200e, volatile u32* addr, u32 val, int msecs) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(msecs); + int ok; + + mb(); + do { + if ((ok = (*addr == val)) || (*addr & STATUS_ERROR)) + break; + + } while (time_before(jiffies, timeout)); + +#if 1 + if (!ok) { + printk(FORE200E "cmd polling failed, got status 0x%08x, expected 0x%08x\n", + *addr, val); + } +#endif + + return ok; +} + + +static int +fore200e_io_poll(struct fore200e* fore200e, volatile u32 __iomem *addr, u32 val, int msecs) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(msecs); + int ok; + + do { + if ((ok = (fore200e->bus->read(addr) == val))) + break; + + } while (time_before(jiffies, timeout)); + +#if 1 + if (!ok) { + printk(FORE200E "I/O polling failed, got status 0x%08x, expected 0x%08x\n", + fore200e->bus->read(addr), val); + } +#endif + + return ok; +} + + +static void +fore200e_free_rx_buf(struct fore200e* fore200e) +{ + int scheme, magn, nbr; + struct buffer* buffer; + + for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) { + for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) { + + if ((buffer = fore200e->host_bsq[ scheme ][ magn ].buffer) != NULL) { + + for (nbr = 0; nbr < fore200e_rx_buf_nbr[ scheme ][ magn ]; nbr++) { + + struct chunk* data = &buffer[ nbr ].data; + + if (data->alloc_addr != NULL) + fore200e_chunk_free(fore200e, data); + } + } + } + } +} + + +static void +fore200e_uninit_bs_queue(struct fore200e* fore200e) +{ + int scheme, magn; + + for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) { + for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) { + + struct chunk* status = &fore200e->host_bsq[ scheme ][ magn ].status; + struct chunk* rbd_block = &fore200e->host_bsq[ scheme ][ magn ].rbd_block; + + if (status->alloc_addr) + fore200e_dma_chunk_free(fore200e, status); + + if (rbd_block->alloc_addr) + fore200e_dma_chunk_free(fore200e, rbd_block); + } + } +} + + +static int +fore200e_reset(struct fore200e* fore200e, int diag) +{ + int ok; + + fore200e->cp_monitor = fore200e->virt_base + FORE200E_CP_MONITOR_OFFSET; + + fore200e->bus->write(BSTAT_COLD_START, &fore200e->cp_monitor->bstat); + + fore200e->bus->reset(fore200e); + + if (diag) { + ok = fore200e_io_poll(fore200e, &fore200e->cp_monitor->bstat, BSTAT_SELFTEST_OK, 1000); + if (ok == 0) { + + printk(FORE200E "device %s self-test failed\n", fore200e->name); + return -ENODEV; + } + + printk(FORE200E "device %s self-test passed\n", fore200e->name); + + fore200e->state = FORE200E_STATE_RESET; + } + + return 0; +} + + +static void +fore200e_shutdown(struct fore200e* fore200e) +{ + printk(FORE200E "removing device %s at 0x%lx, IRQ %s\n", + fore200e->name, fore200e->phys_base, + fore200e_irq_itoa(fore200e->irq)); + + if (fore200e->state > FORE200E_STATE_RESET) { + /* first, reset the board to prevent further interrupts or data transfers */ + fore200e_reset(fore200e, 0); + } + + /* then, release all allocated resources */ + switch(fore200e->state) { + + case FORE200E_STATE_COMPLETE: + kfree(fore200e->stats); + + fallthrough; + case FORE200E_STATE_IRQ: + free_irq(fore200e->irq, fore200e->atm_dev); + + fallthrough; + case FORE200E_STATE_ALLOC_BUF: + fore200e_free_rx_buf(fore200e); + + fallthrough; + case FORE200E_STATE_INIT_BSQ: + fore200e_uninit_bs_queue(fore200e); + + fallthrough; + case FORE200E_STATE_INIT_RXQ: + fore200e_dma_chunk_free(fore200e, &fore200e->host_rxq.status); + fore200e_dma_chunk_free(fore200e, &fore200e->host_rxq.rpd); + + fallthrough; + case FORE200E_STATE_INIT_TXQ: + fore200e_dma_chunk_free(fore200e, &fore200e->host_txq.status); + fore200e_dma_chunk_free(fore200e, &fore200e->host_txq.tpd); + + fallthrough; + case FORE200E_STATE_INIT_CMDQ: + fore200e_dma_chunk_free(fore200e, &fore200e->host_cmdq.status); + + fallthrough; + case FORE200E_STATE_INITIALIZE: + /* nothing to do for that state */ + + case FORE200E_STATE_START_FW: + /* nothing to do for that state */ + + case FORE200E_STATE_RESET: + /* nothing to do for that state */ + + case FORE200E_STATE_MAP: + fore200e->bus->unmap(fore200e); + + fallthrough; + case FORE200E_STATE_CONFIGURE: + /* nothing to do for that state */ + + case FORE200E_STATE_REGISTER: + /* XXX shouldn't we *start* by deregistering the device? */ + atm_dev_deregister(fore200e->atm_dev); + + case FORE200E_STATE_BLANK: + /* nothing to do for that state */ + break; + } +} + + +#ifdef CONFIG_PCI + +static u32 fore200e_pca_read(volatile u32 __iomem *addr) +{ + /* on big-endian hosts, the board is configured to convert + the endianess of slave RAM accesses */ + return le32_to_cpu(readl(addr)); +} + + +static void fore200e_pca_write(u32 val, volatile u32 __iomem *addr) +{ + /* on big-endian hosts, the board is configured to convert + the endianess of slave RAM accesses */ + writel(cpu_to_le32(val), addr); +} + +static int +fore200e_pca_irq_check(struct fore200e* fore200e) +{ + /* this is a 1 bit register */ + int irq_posted = readl(fore200e->regs.pca.psr); + +#if defined(CONFIG_ATM_FORE200E_DEBUG) && (CONFIG_ATM_FORE200E_DEBUG == 2) + if (irq_posted && (readl(fore200e->regs.pca.hcr) & PCA200E_HCR_OUTFULL)) { + DPRINTK(2,"FIFO OUT full, device %d\n", fore200e->atm_dev->number); + } +#endif + + return irq_posted; +} + + +static void +fore200e_pca_irq_ack(struct fore200e* fore200e) +{ + writel(PCA200E_HCR_CLRINTR, fore200e->regs.pca.hcr); +} + + +static void +fore200e_pca_reset(struct fore200e* fore200e) +{ + writel(PCA200E_HCR_RESET, fore200e->regs.pca.hcr); + fore200e_spin(10); + writel(0, fore200e->regs.pca.hcr); +} + + +static int fore200e_pca_map(struct fore200e* fore200e) +{ + DPRINTK(2, "device %s being mapped in memory\n", fore200e->name); + + fore200e->virt_base = ioremap(fore200e->phys_base, PCA200E_IOSPACE_LENGTH); + + if (fore200e->virt_base == NULL) { + printk(FORE200E "can't map device %s\n", fore200e->name); + return -EFAULT; + } + + DPRINTK(1, "device %s mapped to 0x%p\n", fore200e->name, fore200e->virt_base); + + /* gain access to the PCA specific registers */ + fore200e->regs.pca.hcr = fore200e->virt_base + PCA200E_HCR_OFFSET; + fore200e->regs.pca.imr = fore200e->virt_base + PCA200E_IMR_OFFSET; + fore200e->regs.pca.psr = fore200e->virt_base + PCA200E_PSR_OFFSET; + + fore200e->state = FORE200E_STATE_MAP; + return 0; +} + + +static void +fore200e_pca_unmap(struct fore200e* fore200e) +{ + DPRINTK(2, "device %s being unmapped from memory\n", fore200e->name); + + if (fore200e->virt_base != NULL) + iounmap(fore200e->virt_base); +} + + +static int fore200e_pca_configure(struct fore200e *fore200e) +{ + struct pci_dev *pci_dev = to_pci_dev(fore200e->dev); + u8 master_ctrl, latency; + + DPRINTK(2, "device %s being configured\n", fore200e->name); + + if ((pci_dev->irq == 0) || (pci_dev->irq == 0xFF)) { + printk(FORE200E "incorrect IRQ setting - misconfigured PCI-PCI bridge?\n"); + return -EIO; + } + + pci_read_config_byte(pci_dev, PCA200E_PCI_MASTER_CTRL, &master_ctrl); + + master_ctrl = master_ctrl +#if defined(__BIG_ENDIAN) + /* request the PCA board to convert the endianess of slave RAM accesses */ + | PCA200E_CTRL_CONVERT_ENDIAN +#endif +#if 0 + | PCA200E_CTRL_DIS_CACHE_RD + | PCA200E_CTRL_DIS_WRT_INVAL + | PCA200E_CTRL_ENA_CONT_REQ_MODE + | PCA200E_CTRL_2_CACHE_WRT_INVAL +#endif + | PCA200E_CTRL_LARGE_PCI_BURSTS; + + pci_write_config_byte(pci_dev, PCA200E_PCI_MASTER_CTRL, master_ctrl); + + /* raise latency from 32 (default) to 192, as this seems to prevent NIC + lockups (under heavy rx loads) due to continuous 'FIFO OUT full' condition. + this may impact the performances of other PCI devices on the same bus, though */ + latency = 192; + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + + fore200e->state = FORE200E_STATE_CONFIGURE; + return 0; +} + + +static int __init +fore200e_pca_prom_read(struct fore200e* fore200e, struct prom_data* prom) +{ + struct host_cmdq* cmdq = &fore200e->host_cmdq; + struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ]; + struct prom_opcode opcode; + int ok; + u32 prom_dma; + + FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD); + + opcode.opcode = OPCODE_GET_PROM; + opcode.pad = 0; + + prom_dma = dma_map_single(fore200e->dev, prom, sizeof(struct prom_data), + DMA_FROM_DEVICE); + if (dma_mapping_error(fore200e->dev, prom_dma)) + return -ENOMEM; + + fore200e->bus->write(prom_dma, &entry->cp_entry->cmd.prom_block.prom_haddr); + + *entry->status = STATUS_PENDING; + + fore200e->bus->write(*(u32*)&opcode, (u32 __iomem *)&entry->cp_entry->cmd.prom_block.opcode); + + ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400); + + *entry->status = STATUS_FREE; + + dma_unmap_single(fore200e->dev, prom_dma, sizeof(struct prom_data), DMA_FROM_DEVICE); + + if (ok == 0) { + printk(FORE200E "unable to get PROM data from device %s\n", fore200e->name); + return -EIO; + } + +#if defined(__BIG_ENDIAN) + +#define swap_here(addr) (*((u32*)(addr)) = swab32( *((u32*)(addr)) )) + + /* MAC address is stored as little-endian */ + swap_here(&prom->mac_addr[0]); + swap_here(&prom->mac_addr[4]); +#endif + + return 0; +} + + +static int +fore200e_pca_proc_read(struct fore200e* fore200e, char *page) +{ + struct pci_dev *pci_dev = to_pci_dev(fore200e->dev); + + return sprintf(page, " PCI bus/slot/function:\t%d/%d/%d\n", + pci_dev->bus->number, PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn)); +} + +static const struct fore200e_bus fore200e_pci_ops = { + .model_name = "PCA-200E", + .proc_name = "pca200e", + .descr_alignment = 32, + .buffer_alignment = 4, + .status_alignment = 32, + .read = fore200e_pca_read, + .write = fore200e_pca_write, + .configure = fore200e_pca_configure, + .map = fore200e_pca_map, + .reset = fore200e_pca_reset, + .prom_read = fore200e_pca_prom_read, + .unmap = fore200e_pca_unmap, + .irq_check = fore200e_pca_irq_check, + .irq_ack = fore200e_pca_irq_ack, + .proc_read = fore200e_pca_proc_read, +}; +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_SBUS + +static u32 fore200e_sba_read(volatile u32 __iomem *addr) +{ + return sbus_readl(addr); +} + +static void fore200e_sba_write(u32 val, volatile u32 __iomem *addr) +{ + sbus_writel(val, addr); +} + +static void fore200e_sba_irq_enable(struct fore200e *fore200e) +{ + u32 hcr = fore200e->bus->read(fore200e->regs.sba.hcr) & SBA200E_HCR_STICKY; + fore200e->bus->write(hcr | SBA200E_HCR_INTR_ENA, fore200e->regs.sba.hcr); +} + +static int fore200e_sba_irq_check(struct fore200e *fore200e) +{ + return fore200e->bus->read(fore200e->regs.sba.hcr) & SBA200E_HCR_INTR_REQ; +} + +static void fore200e_sba_irq_ack(struct fore200e *fore200e) +{ + u32 hcr = fore200e->bus->read(fore200e->regs.sba.hcr) & SBA200E_HCR_STICKY; + fore200e->bus->write(hcr | SBA200E_HCR_INTR_CLR, fore200e->regs.sba.hcr); +} + +static void fore200e_sba_reset(struct fore200e *fore200e) +{ + fore200e->bus->write(SBA200E_HCR_RESET, fore200e->regs.sba.hcr); + fore200e_spin(10); + fore200e->bus->write(0, fore200e->regs.sba.hcr); +} + +static int __init fore200e_sba_map(struct fore200e *fore200e) +{ + struct platform_device *op = to_platform_device(fore200e->dev); + unsigned int bursts; + + /* gain access to the SBA specific registers */ + fore200e->regs.sba.hcr = of_ioremap(&op->resource[0], 0, SBA200E_HCR_LENGTH, "SBA HCR"); + fore200e->regs.sba.bsr = of_ioremap(&op->resource[1], 0, SBA200E_BSR_LENGTH, "SBA BSR"); + fore200e->regs.sba.isr = of_ioremap(&op->resource[2], 0, SBA200E_ISR_LENGTH, "SBA ISR"); + fore200e->virt_base = of_ioremap(&op->resource[3], 0, SBA200E_RAM_LENGTH, "SBA RAM"); + + if (!fore200e->virt_base) { + printk(FORE200E "unable to map RAM of device %s\n", fore200e->name); + return -EFAULT; + } + + DPRINTK(1, "device %s mapped to 0x%p\n", fore200e->name, fore200e->virt_base); + + fore200e->bus->write(0x02, fore200e->regs.sba.isr); /* XXX hardwired interrupt level */ + + /* get the supported DVMA burst sizes */ + bursts = of_getintprop_default(op->dev.of_node->parent, "burst-sizes", 0x00); + + if (sbus_can_dma_64bit()) + sbus_set_sbus64(&op->dev, bursts); + + fore200e->state = FORE200E_STATE_MAP; + return 0; +} + +static void fore200e_sba_unmap(struct fore200e *fore200e) +{ + struct platform_device *op = to_platform_device(fore200e->dev); + + of_iounmap(&op->resource[0], fore200e->regs.sba.hcr, SBA200E_HCR_LENGTH); + of_iounmap(&op->resource[1], fore200e->regs.sba.bsr, SBA200E_BSR_LENGTH); + of_iounmap(&op->resource[2], fore200e->regs.sba.isr, SBA200E_ISR_LENGTH); + of_iounmap(&op->resource[3], fore200e->virt_base, SBA200E_RAM_LENGTH); +} + +static int __init fore200e_sba_configure(struct fore200e *fore200e) +{ + fore200e->state = FORE200E_STATE_CONFIGURE; + return 0; +} + +static int __init fore200e_sba_prom_read(struct fore200e *fore200e, struct prom_data *prom) +{ + struct platform_device *op = to_platform_device(fore200e->dev); + const u8 *prop; + int len; + + prop = of_get_property(op->dev.of_node, "madaddrlo2", &len); + if (!prop) + return -ENODEV; + memcpy(&prom->mac_addr[4], prop, 4); + + prop = of_get_property(op->dev.of_node, "madaddrhi4", &len); + if (!prop) + return -ENODEV; + memcpy(&prom->mac_addr[2], prop, 4); + + prom->serial_number = of_getintprop_default(op->dev.of_node, + "serialnumber", 0); + prom->hw_revision = of_getintprop_default(op->dev.of_node, + "promversion", 0); + + return 0; +} + +static int fore200e_sba_proc_read(struct fore200e *fore200e, char *page) +{ + struct platform_device *op = to_platform_device(fore200e->dev); + const struct linux_prom_registers *regs; + + regs = of_get_property(op->dev.of_node, "reg", NULL); + + return sprintf(page, " SBUS slot/device:\t\t%d/'%pOFn'\n", + (regs ? regs->which_io : 0), op->dev.of_node); +} + +static const struct fore200e_bus fore200e_sbus_ops = { + .model_name = "SBA-200E", + .proc_name = "sba200e", + .descr_alignment = 32, + .buffer_alignment = 64, + .status_alignment = 32, + .read = fore200e_sba_read, + .write = fore200e_sba_write, + .configure = fore200e_sba_configure, + .map = fore200e_sba_map, + .reset = fore200e_sba_reset, + .prom_read = fore200e_sba_prom_read, + .unmap = fore200e_sba_unmap, + .irq_enable = fore200e_sba_irq_enable, + .irq_check = fore200e_sba_irq_check, + .irq_ack = fore200e_sba_irq_ack, + .proc_read = fore200e_sba_proc_read, +}; +#endif /* CONFIG_SBUS */ + +static void +fore200e_tx_irq(struct fore200e* fore200e) +{ + struct host_txq* txq = &fore200e->host_txq; + struct host_txq_entry* entry; + struct atm_vcc* vcc; + struct fore200e_vc_map* vc_map; + + if (fore200e->host_txq.txing == 0) + return; + + for (;;) { + + entry = &txq->host_entry[ txq->tail ]; + + if ((*entry->status & STATUS_COMPLETE) == 0) { + break; + } + + DPRINTK(3, "TX COMPLETED: entry = %p [tail = %d], vc_map = %p, skb = %p\n", + entry, txq->tail, entry->vc_map, entry->skb); + + /* free copy of misaligned data */ + kfree(entry->data); + + /* remove DMA mapping */ + dma_unmap_single(fore200e->dev, entry->tpd->tsd[ 0 ].buffer, entry->tpd->tsd[ 0 ].length, + DMA_TO_DEVICE); + + vc_map = entry->vc_map; + + /* vcc closed since the time the entry was submitted for tx? */ + if ((vc_map->vcc == NULL) || + (test_bit(ATM_VF_READY, &vc_map->vcc->flags) == 0)) { + + DPRINTK(1, "no ready vcc found for PDU sent on device %d\n", + fore200e->atm_dev->number); + + dev_kfree_skb_any(entry->skb); + } + else { + ASSERT(vc_map->vcc); + + /* vcc closed then immediately re-opened? */ + if (vc_map->incarn != entry->incarn) { + + /* when a vcc is closed, some PDUs may be still pending in the tx queue. + if the same vcc is immediately re-opened, those pending PDUs must + not be popped after the completion of their emission, as they refer + to the prior incarnation of that vcc. otherwise, sk_atm(vcc)->sk_wmem_alloc + would be decremented by the size of the (unrelated) skb, possibly + leading to a negative sk->sk_wmem_alloc count, ultimately freezing the vcc. + we thus bind the tx entry to the current incarnation of the vcc + when the entry is submitted for tx. When the tx later completes, + if the incarnation number of the tx entry does not match the one + of the vcc, then this implies that the vcc has been closed then re-opened. + we thus just drop the skb here. */ + + DPRINTK(1, "vcc closed-then-re-opened; dropping PDU sent on device %d\n", + fore200e->atm_dev->number); + + dev_kfree_skb_any(entry->skb); + } + else { + vcc = vc_map->vcc; + ASSERT(vcc); + + /* notify tx completion */ + if (vcc->pop) { + vcc->pop(vcc, entry->skb); + } + else { + dev_kfree_skb_any(entry->skb); + } + + /* check error condition */ + if (*entry->status & STATUS_ERROR) + atomic_inc(&vcc->stats->tx_err); + else + atomic_inc(&vcc->stats->tx); + } + } + + *entry->status = STATUS_FREE; + + fore200e->host_txq.txing--; + + FORE200E_NEXT_ENTRY(txq->tail, QUEUE_SIZE_TX); + } +} + + +#ifdef FORE200E_BSQ_DEBUG +int bsq_audit(int where, struct host_bsq* bsq, int scheme, int magn) +{ + struct buffer* buffer; + int count = 0; + + buffer = bsq->freebuf; + while (buffer) { + + if (buffer->supplied) { + printk(FORE200E "bsq_audit(%d): queue %d.%d, buffer %ld supplied but in free list!\n", + where, scheme, magn, buffer->index); + } + + if (buffer->magn != magn) { + printk(FORE200E "bsq_audit(%d): queue %d.%d, buffer %ld, unexpected magn = %d\n", + where, scheme, magn, buffer->index, buffer->magn); + } + + if (buffer->scheme != scheme) { + printk(FORE200E "bsq_audit(%d): queue %d.%d, buffer %ld, unexpected scheme = %d\n", + where, scheme, magn, buffer->index, buffer->scheme); + } + + if ((buffer->index < 0) || (buffer->index >= fore200e_rx_buf_nbr[ scheme ][ magn ])) { + printk(FORE200E "bsq_audit(%d): queue %d.%d, out of range buffer index = %ld !\n", + where, scheme, magn, buffer->index); + } + + count++; + buffer = buffer->next; + } + + if (count != bsq->freebuf_count) { + printk(FORE200E "bsq_audit(%d): queue %d.%d, %d bufs in free list, but freebuf_count = %d\n", + where, scheme, magn, count, bsq->freebuf_count); + } + return 0; +} +#endif + + +static void +fore200e_supply(struct fore200e* fore200e) +{ + int scheme, magn, i; + + struct host_bsq* bsq; + struct host_bsq_entry* entry; + struct buffer* buffer; + + for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) { + for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) { + + bsq = &fore200e->host_bsq[ scheme ][ magn ]; + +#ifdef FORE200E_BSQ_DEBUG + bsq_audit(1, bsq, scheme, magn); +#endif + while (bsq->freebuf_count >= RBD_BLK_SIZE) { + + DPRINTK(2, "supplying %d rx buffers to queue %d / %d, freebuf_count = %d\n", + RBD_BLK_SIZE, scheme, magn, bsq->freebuf_count); + + entry = &bsq->host_entry[ bsq->head ]; + + for (i = 0; i < RBD_BLK_SIZE; i++) { + + /* take the first buffer in the free buffer list */ + buffer = bsq->freebuf; + if (!buffer) { + printk(FORE200E "no more free bufs in queue %d.%d, but freebuf_count = %d\n", + scheme, magn, bsq->freebuf_count); + return; + } + bsq->freebuf = buffer->next; + +#ifdef FORE200E_BSQ_DEBUG + if (buffer->supplied) + printk(FORE200E "queue %d.%d, buffer %lu already supplied\n", + scheme, magn, buffer->index); + buffer->supplied = 1; +#endif + entry->rbd_block->rbd[ i ].buffer_haddr = buffer->data.dma_addr; + entry->rbd_block->rbd[ i ].handle = FORE200E_BUF2HDL(buffer); + } + + FORE200E_NEXT_ENTRY(bsq->head, QUEUE_SIZE_BS); + + /* decrease accordingly the number of free rx buffers */ + bsq->freebuf_count -= RBD_BLK_SIZE; + + *entry->status = STATUS_PENDING; + fore200e->bus->write(entry->rbd_block_dma, &entry->cp_entry->rbd_block_haddr); + } + } + } +} + + +static int +fore200e_push_rpd(struct fore200e* fore200e, struct atm_vcc* vcc, struct rpd* rpd) +{ + struct sk_buff* skb; + struct buffer* buffer; + struct fore200e_vcc* fore200e_vcc; + int i, pdu_len = 0; +#ifdef FORE200E_52BYTE_AAL0_SDU + u32 cell_header = 0; +#endif + + ASSERT(vcc); + + fore200e_vcc = FORE200E_VCC(vcc); + ASSERT(fore200e_vcc); + +#ifdef FORE200E_52BYTE_AAL0_SDU + if ((vcc->qos.aal == ATM_AAL0) && (vcc->qos.rxtp.max_sdu == ATM_AAL0_SDU)) { + + cell_header = (rpd->atm_header.gfc << ATM_HDR_GFC_SHIFT) | + (rpd->atm_header.vpi << ATM_HDR_VPI_SHIFT) | + (rpd->atm_header.vci << ATM_HDR_VCI_SHIFT) | + (rpd->atm_header.plt << ATM_HDR_PTI_SHIFT) | + rpd->atm_header.clp; + pdu_len = 4; + } +#endif + + /* compute total PDU length */ + for (i = 0; i < rpd->nseg; i++) + pdu_len += rpd->rsd[ i ].length; + + skb = alloc_skb(pdu_len, GFP_ATOMIC); + if (skb == NULL) { + DPRINTK(2, "unable to alloc new skb, rx PDU length = %d\n", pdu_len); + + atomic_inc(&vcc->stats->rx_drop); + return -ENOMEM; + } + + __net_timestamp(skb); + +#ifdef FORE200E_52BYTE_AAL0_SDU + if (cell_header) { + *((u32*)skb_put(skb, 4)) = cell_header; + } +#endif + + /* reassemble segments */ + for (i = 0; i < rpd->nseg; i++) { + + /* rebuild rx buffer address from rsd handle */ + buffer = FORE200E_HDL2BUF(rpd->rsd[ i ].handle); + + /* Make device DMA transfer visible to CPU. */ + dma_sync_single_for_cpu(fore200e->dev, buffer->data.dma_addr, + rpd->rsd[i].length, DMA_FROM_DEVICE); + + skb_put_data(skb, buffer->data.align_addr, rpd->rsd[i].length); + + /* Now let the device get at it again. */ + dma_sync_single_for_device(fore200e->dev, buffer->data.dma_addr, + rpd->rsd[i].length, DMA_FROM_DEVICE); + } + + DPRINTK(3, "rx skb: len = %d, truesize = %d\n", skb->len, skb->truesize); + + if (pdu_len < fore200e_vcc->rx_min_pdu) + fore200e_vcc->rx_min_pdu = pdu_len; + if (pdu_len > fore200e_vcc->rx_max_pdu) + fore200e_vcc->rx_max_pdu = pdu_len; + fore200e_vcc->rx_pdu++; + + /* push PDU */ + if (atm_charge(vcc, skb->truesize) == 0) { + + DPRINTK(2, "receive buffers saturated for %d.%d.%d - PDU dropped\n", + vcc->itf, vcc->vpi, vcc->vci); + + dev_kfree_skb_any(skb); + + atomic_inc(&vcc->stats->rx_drop); + return -ENOMEM; + } + + vcc->push(vcc, skb); + atomic_inc(&vcc->stats->rx); + + return 0; +} + + +static void +fore200e_collect_rpd(struct fore200e* fore200e, struct rpd* rpd) +{ + struct host_bsq* bsq; + struct buffer* buffer; + int i; + + for (i = 0; i < rpd->nseg; i++) { + + /* rebuild rx buffer address from rsd handle */ + buffer = FORE200E_HDL2BUF(rpd->rsd[ i ].handle); + + bsq = &fore200e->host_bsq[ buffer->scheme ][ buffer->magn ]; + +#ifdef FORE200E_BSQ_DEBUG + bsq_audit(2, bsq, buffer->scheme, buffer->magn); + + if (buffer->supplied == 0) + printk(FORE200E "queue %d.%d, buffer %ld was not supplied\n", + buffer->scheme, buffer->magn, buffer->index); + buffer->supplied = 0; +#endif + + /* re-insert the buffer into the free buffer list */ + buffer->next = bsq->freebuf; + bsq->freebuf = buffer; + + /* then increment the number of free rx buffers */ + bsq->freebuf_count++; + } +} + + +static void +fore200e_rx_irq(struct fore200e* fore200e) +{ + struct host_rxq* rxq = &fore200e->host_rxq; + struct host_rxq_entry* entry; + struct atm_vcc* vcc; + struct fore200e_vc_map* vc_map; + + for (;;) { + + entry = &rxq->host_entry[ rxq->head ]; + + /* no more received PDUs */ + if ((*entry->status & STATUS_COMPLETE) == 0) + break; + + vc_map = FORE200E_VC_MAP(fore200e, entry->rpd->atm_header.vpi, entry->rpd->atm_header.vci); + + if ((vc_map->vcc == NULL) || + (test_bit(ATM_VF_READY, &vc_map->vcc->flags) == 0)) { + + DPRINTK(1, "no ready VC found for PDU received on %d.%d.%d\n", + fore200e->atm_dev->number, + entry->rpd->atm_header.vpi, entry->rpd->atm_header.vci); + } + else { + vcc = vc_map->vcc; + ASSERT(vcc); + + if ((*entry->status & STATUS_ERROR) == 0) { + + fore200e_push_rpd(fore200e, vcc, entry->rpd); + } + else { + DPRINTK(2, "damaged PDU on %d.%d.%d\n", + fore200e->atm_dev->number, + entry->rpd->atm_header.vpi, entry->rpd->atm_header.vci); + atomic_inc(&vcc->stats->rx_err); + } + } + + FORE200E_NEXT_ENTRY(rxq->head, QUEUE_SIZE_RX); + + fore200e_collect_rpd(fore200e, entry->rpd); + + /* rewrite the rpd address to ack the received PDU */ + fore200e->bus->write(entry->rpd_dma, &entry->cp_entry->rpd_haddr); + *entry->status = STATUS_FREE; + + fore200e_supply(fore200e); + } +} + + +#ifndef FORE200E_USE_TASKLET +static void +fore200e_irq(struct fore200e* fore200e) +{ + unsigned long flags; + + spin_lock_irqsave(&fore200e->q_lock, flags); + fore200e_rx_irq(fore200e); + spin_unlock_irqrestore(&fore200e->q_lock, flags); + + spin_lock_irqsave(&fore200e->q_lock, flags); + fore200e_tx_irq(fore200e); + spin_unlock_irqrestore(&fore200e->q_lock, flags); +} +#endif + + +static irqreturn_t +fore200e_interrupt(int irq, void* dev) +{ + struct fore200e* fore200e = FORE200E_DEV((struct atm_dev*)dev); + + if (fore200e->bus->irq_check(fore200e) == 0) { + + DPRINTK(3, "interrupt NOT triggered by device %d\n", fore200e->atm_dev->number); + return IRQ_NONE; + } + DPRINTK(3, "interrupt triggered by device %d\n", fore200e->atm_dev->number); + +#ifdef FORE200E_USE_TASKLET + tasklet_schedule(&fore200e->tx_tasklet); + tasklet_schedule(&fore200e->rx_tasklet); +#else + fore200e_irq(fore200e); +#endif + + fore200e->bus->irq_ack(fore200e); + return IRQ_HANDLED; +} + + +#ifdef FORE200E_USE_TASKLET +static void +fore200e_tx_tasklet(unsigned long data) +{ + struct fore200e* fore200e = (struct fore200e*) data; + unsigned long flags; + + DPRINTK(3, "tx tasklet scheduled for device %d\n", fore200e->atm_dev->number); + + spin_lock_irqsave(&fore200e->q_lock, flags); + fore200e_tx_irq(fore200e); + spin_unlock_irqrestore(&fore200e->q_lock, flags); +} + + +static void +fore200e_rx_tasklet(unsigned long data) +{ + struct fore200e* fore200e = (struct fore200e*) data; + unsigned long flags; + + DPRINTK(3, "rx tasklet scheduled for device %d\n", fore200e->atm_dev->number); + + spin_lock_irqsave(&fore200e->q_lock, flags); + fore200e_rx_irq((struct fore200e*) data); + spin_unlock_irqrestore(&fore200e->q_lock, flags); +} +#endif + + +static int +fore200e_select_scheme(struct atm_vcc* vcc) +{ + /* fairly balance the VCs over (identical) buffer schemes */ + int scheme = vcc->vci % 2 ? BUFFER_SCHEME_ONE : BUFFER_SCHEME_TWO; + + DPRINTK(1, "VC %d.%d.%d uses buffer scheme %d\n", + vcc->itf, vcc->vpi, vcc->vci, scheme); + + return scheme; +} + + +static int +fore200e_activate_vcin(struct fore200e* fore200e, int activate, struct atm_vcc* vcc, int mtu) +{ + struct host_cmdq* cmdq = &fore200e->host_cmdq; + struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ]; + struct activate_opcode activ_opcode; + struct deactivate_opcode deactiv_opcode; + struct vpvc vpvc; + int ok; + enum fore200e_aal aal = fore200e_atm2fore_aal(vcc->qos.aal); + + FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD); + + if (activate) { + FORE200E_VCC(vcc)->scheme = fore200e_select_scheme(vcc); + + activ_opcode.opcode = OPCODE_ACTIVATE_VCIN; + activ_opcode.aal = aal; + activ_opcode.scheme = FORE200E_VCC(vcc)->scheme; + activ_opcode.pad = 0; + } + else { + deactiv_opcode.opcode = OPCODE_DEACTIVATE_VCIN; + deactiv_opcode.pad = 0; + } + + vpvc.vci = vcc->vci; + vpvc.vpi = vcc->vpi; + + *entry->status = STATUS_PENDING; + + if (activate) { + +#ifdef FORE200E_52BYTE_AAL0_SDU + mtu = 48; +#endif + /* the MTU is not used by the cp, except in the case of AAL0 */ + fore200e->bus->write(mtu, &entry->cp_entry->cmd.activate_block.mtu); + fore200e->bus->write(*(u32*)&vpvc, (u32 __iomem *)&entry->cp_entry->cmd.activate_block.vpvc); + fore200e->bus->write(*(u32*)&activ_opcode, (u32 __iomem *)&entry->cp_entry->cmd.activate_block.opcode); + } + else { + fore200e->bus->write(*(u32*)&vpvc, (u32 __iomem *)&entry->cp_entry->cmd.deactivate_block.vpvc); + fore200e->bus->write(*(u32*)&deactiv_opcode, (u32 __iomem *)&entry->cp_entry->cmd.deactivate_block.opcode); + } + + ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400); + + *entry->status = STATUS_FREE; + + if (ok == 0) { + printk(FORE200E "unable to %s VC %d.%d.%d\n", + activate ? "open" : "close", vcc->itf, vcc->vpi, vcc->vci); + return -EIO; + } + + DPRINTK(1, "VC %d.%d.%d %sed\n", vcc->itf, vcc->vpi, vcc->vci, + activate ? "open" : "clos"); + + return 0; +} + + +#define FORE200E_MAX_BACK2BACK_CELLS 255 /* XXX depends on CDVT */ + +static void +fore200e_rate_ctrl(struct atm_qos* qos, struct tpd_rate* rate) +{ + if (qos->txtp.max_pcr < ATM_OC3_PCR) { + + /* compute the data cells to idle cells ratio from the tx PCR */ + rate->data_cells = qos->txtp.max_pcr * FORE200E_MAX_BACK2BACK_CELLS / ATM_OC3_PCR; + rate->idle_cells = FORE200E_MAX_BACK2BACK_CELLS - rate->data_cells; + } + else { + /* disable rate control */ + rate->data_cells = rate->idle_cells = 0; + } +} + + +static int +fore200e_open(struct atm_vcc *vcc) +{ + struct fore200e* fore200e = FORE200E_DEV(vcc->dev); + struct fore200e_vcc* fore200e_vcc; + struct fore200e_vc_map* vc_map; + unsigned long flags; + int vci = vcc->vci; + short vpi = vcc->vpi; + + ASSERT((vpi >= 0) && (vpi < 1<= 0) && (vci < 1<q_lock, flags); + + vc_map = FORE200E_VC_MAP(fore200e, vpi, vci); + if (vc_map->vcc) { + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + + printk(FORE200E "VC %d.%d.%d already in use\n", + fore200e->atm_dev->number, vpi, vci); + + return -EINVAL; + } + + vc_map->vcc = vcc; + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + + fore200e_vcc = kzalloc(sizeof(struct fore200e_vcc), GFP_ATOMIC); + if (fore200e_vcc == NULL) { + vc_map->vcc = NULL; + return -ENOMEM; + } + + DPRINTK(2, "opening %d.%d.%d:%d QoS = (tx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d; " + "rx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d)\n", + vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal), + fore200e_traffic_class[ vcc->qos.txtp.traffic_class ], + vcc->qos.txtp.min_pcr, vcc->qos.txtp.max_pcr, vcc->qos.txtp.max_cdv, vcc->qos.txtp.max_sdu, + fore200e_traffic_class[ vcc->qos.rxtp.traffic_class ], + vcc->qos.rxtp.min_pcr, vcc->qos.rxtp.max_pcr, vcc->qos.rxtp.max_cdv, vcc->qos.rxtp.max_sdu); + + /* pseudo-CBR bandwidth requested? */ + if ((vcc->qos.txtp.traffic_class == ATM_CBR) && (vcc->qos.txtp.max_pcr > 0)) { + + mutex_lock(&fore200e->rate_mtx); + if (fore200e->available_cell_rate < vcc->qos.txtp.max_pcr) { + mutex_unlock(&fore200e->rate_mtx); + + kfree(fore200e_vcc); + vc_map->vcc = NULL; + return -EAGAIN; + } + + /* reserve bandwidth */ + fore200e->available_cell_rate -= vcc->qos.txtp.max_pcr; + mutex_unlock(&fore200e->rate_mtx); + } + + vcc->itf = vcc->dev->number; + + set_bit(ATM_VF_PARTIAL,&vcc->flags); + set_bit(ATM_VF_ADDR, &vcc->flags); + + vcc->dev_data = fore200e_vcc; + + if (fore200e_activate_vcin(fore200e, 1, vcc, vcc->qos.rxtp.max_sdu) < 0) { + + vc_map->vcc = NULL; + + clear_bit(ATM_VF_ADDR, &vcc->flags); + clear_bit(ATM_VF_PARTIAL,&vcc->flags); + + vcc->dev_data = NULL; + + fore200e->available_cell_rate += vcc->qos.txtp.max_pcr; + + kfree(fore200e_vcc); + return -EINVAL; + } + + /* compute rate control parameters */ + if ((vcc->qos.txtp.traffic_class == ATM_CBR) && (vcc->qos.txtp.max_pcr > 0)) { + + fore200e_rate_ctrl(&vcc->qos, &fore200e_vcc->rate); + set_bit(ATM_VF_HASQOS, &vcc->flags); + + DPRINTK(3, "tx on %d.%d.%d:%d, tx PCR = %d, rx PCR = %d, data_cells = %u, idle_cells = %u\n", + vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal), + vcc->qos.txtp.max_pcr, vcc->qos.rxtp.max_pcr, + fore200e_vcc->rate.data_cells, fore200e_vcc->rate.idle_cells); + } + + fore200e_vcc->tx_min_pdu = fore200e_vcc->rx_min_pdu = MAX_PDU_SIZE + 1; + fore200e_vcc->tx_max_pdu = fore200e_vcc->rx_max_pdu = 0; + fore200e_vcc->tx_pdu = fore200e_vcc->rx_pdu = 0; + + /* new incarnation of the vcc */ + vc_map->incarn = ++fore200e->incarn_count; + + /* VC unusable before this flag is set */ + set_bit(ATM_VF_READY, &vcc->flags); + + return 0; +} + + +static void +fore200e_close(struct atm_vcc* vcc) +{ + struct fore200e_vcc* fore200e_vcc; + struct fore200e* fore200e; + struct fore200e_vc_map* vc_map; + unsigned long flags; + + ASSERT(vcc); + fore200e = FORE200E_DEV(vcc->dev); + + ASSERT((vcc->vpi >= 0) && (vcc->vpi < 1<vci >= 0) && (vcc->vci < 1<itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal)); + + clear_bit(ATM_VF_READY, &vcc->flags); + + fore200e_activate_vcin(fore200e, 0, vcc, 0); + + spin_lock_irqsave(&fore200e->q_lock, flags); + + vc_map = FORE200E_VC_MAP(fore200e, vcc->vpi, vcc->vci); + + /* the vc is no longer considered as "in use" by fore200e_open() */ + vc_map->vcc = NULL; + + vcc->itf = vcc->vci = vcc->vpi = 0; + + fore200e_vcc = FORE200E_VCC(vcc); + vcc->dev_data = NULL; + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + + /* release reserved bandwidth, if any */ + if ((vcc->qos.txtp.traffic_class == ATM_CBR) && (vcc->qos.txtp.max_pcr > 0)) { + + mutex_lock(&fore200e->rate_mtx); + fore200e->available_cell_rate += vcc->qos.txtp.max_pcr; + mutex_unlock(&fore200e->rate_mtx); + + clear_bit(ATM_VF_HASQOS, &vcc->flags); + } + + clear_bit(ATM_VF_ADDR, &vcc->flags); + clear_bit(ATM_VF_PARTIAL,&vcc->flags); + + ASSERT(fore200e_vcc); + kfree(fore200e_vcc); +} + + +static int +fore200e_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct fore200e* fore200e; + struct fore200e_vcc* fore200e_vcc; + struct fore200e_vc_map* vc_map; + struct host_txq* txq; + struct host_txq_entry* entry; + struct tpd* tpd; + struct tpd_haddr tpd_haddr; + int retry = CONFIG_ATM_FORE200E_TX_RETRY; + int tx_copy = 0; + int tx_len = skb->len; + u32* cell_header = NULL; + unsigned char* skb_data; + int skb_len; + unsigned char* data; + unsigned long flags; + + if (!vcc) + return -EINVAL; + + fore200e = FORE200E_DEV(vcc->dev); + fore200e_vcc = FORE200E_VCC(vcc); + + if (!fore200e) + return -EINVAL; + + txq = &fore200e->host_txq; + if (!fore200e_vcc) + return -EINVAL; + + if (!test_bit(ATM_VF_READY, &vcc->flags)) { + DPRINTK(1, "VC %d.%d.%d not ready for tx\n", vcc->itf, vcc->vpi, vcc->vpi); + dev_kfree_skb_any(skb); + return -EINVAL; + } + +#ifdef FORE200E_52BYTE_AAL0_SDU + if ((vcc->qos.aal == ATM_AAL0) && (vcc->qos.txtp.max_sdu == ATM_AAL0_SDU)) { + cell_header = (u32*) skb->data; + skb_data = skb->data + 4; /* skip 4-byte cell header */ + skb_len = tx_len = skb->len - 4; + + DPRINTK(3, "user-supplied cell header = 0x%08x\n", *cell_header); + } + else +#endif + { + skb_data = skb->data; + skb_len = skb->len; + } + + if (((unsigned long)skb_data) & 0x3) { + + DPRINTK(2, "misaligned tx PDU on device %s\n", fore200e->name); + tx_copy = 1; + tx_len = skb_len; + } + + if ((vcc->qos.aal == ATM_AAL0) && (skb_len % ATM_CELL_PAYLOAD)) { + + /* this simply NUKES the PCA board */ + DPRINTK(2, "incomplete tx AAL0 PDU on device %s\n", fore200e->name); + tx_copy = 1; + tx_len = ((skb_len / ATM_CELL_PAYLOAD) + 1) * ATM_CELL_PAYLOAD; + } + + if (tx_copy) { + data = kmalloc(tx_len, GFP_ATOMIC); + if (data == NULL) { + if (vcc->pop) { + vcc->pop(vcc, skb); + } + else { + dev_kfree_skb_any(skb); + } + return -ENOMEM; + } + + memcpy(data, skb_data, skb_len); + if (skb_len < tx_len) + memset(data + skb_len, 0x00, tx_len - skb_len); + } + else { + data = skb_data; + } + + vc_map = FORE200E_VC_MAP(fore200e, vcc->vpi, vcc->vci); + ASSERT(vc_map->vcc == vcc); + + retry_here: + + spin_lock_irqsave(&fore200e->q_lock, flags); + + entry = &txq->host_entry[ txq->head ]; + + if ((*entry->status != STATUS_FREE) || (txq->txing >= QUEUE_SIZE_TX - 2)) { + + /* try to free completed tx queue entries */ + fore200e_tx_irq(fore200e); + + if (*entry->status != STATUS_FREE) { + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + + /* retry once again? */ + if (--retry > 0) { + udelay(50); + goto retry_here; + } + + atomic_inc(&vcc->stats->tx_err); + + fore200e->tx_sat++; + DPRINTK(2, "tx queue of device %s is saturated, PDU dropped - heartbeat is %08x\n", + fore200e->name, fore200e->cp_queues->heartbeat); + if (vcc->pop) { + vcc->pop(vcc, skb); + } + else { + dev_kfree_skb_any(skb); + } + + if (tx_copy) + kfree(data); + + return -ENOBUFS; + } + } + + entry->incarn = vc_map->incarn; + entry->vc_map = vc_map; + entry->skb = skb; + entry->data = tx_copy ? data : NULL; + + tpd = entry->tpd; + tpd->tsd[ 0 ].buffer = dma_map_single(fore200e->dev, data, tx_len, + DMA_TO_DEVICE); + if (dma_mapping_error(fore200e->dev, tpd->tsd[0].buffer)) { + if (tx_copy) + kfree(data); + spin_unlock_irqrestore(&fore200e->q_lock, flags); + return -ENOMEM; + } + tpd->tsd[ 0 ].length = tx_len; + + FORE200E_NEXT_ENTRY(txq->head, QUEUE_SIZE_TX); + txq->txing++; + + /* The dma_map call above implies a dma_sync so the device can use it, + * thus no explicit dma_sync call is necessary here. + */ + + DPRINTK(3, "tx on %d.%d.%d:%d, len = %u (%u)\n", + vcc->itf, vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal), + tpd->tsd[0].length, skb_len); + + if (skb_len < fore200e_vcc->tx_min_pdu) + fore200e_vcc->tx_min_pdu = skb_len; + if (skb_len > fore200e_vcc->tx_max_pdu) + fore200e_vcc->tx_max_pdu = skb_len; + fore200e_vcc->tx_pdu++; + + /* set tx rate control information */ + tpd->rate.data_cells = fore200e_vcc->rate.data_cells; + tpd->rate.idle_cells = fore200e_vcc->rate.idle_cells; + + if (cell_header) { + tpd->atm_header.clp = (*cell_header & ATM_HDR_CLP); + tpd->atm_header.plt = (*cell_header & ATM_HDR_PTI_MASK) >> ATM_HDR_PTI_SHIFT; + tpd->atm_header.vci = (*cell_header & ATM_HDR_VCI_MASK) >> ATM_HDR_VCI_SHIFT; + tpd->atm_header.vpi = (*cell_header & ATM_HDR_VPI_MASK) >> ATM_HDR_VPI_SHIFT; + tpd->atm_header.gfc = (*cell_header & ATM_HDR_GFC_MASK) >> ATM_HDR_GFC_SHIFT; + } + else { + /* set the ATM header, common to all cells conveying the PDU */ + tpd->atm_header.clp = 0; + tpd->atm_header.plt = 0; + tpd->atm_header.vci = vcc->vci; + tpd->atm_header.vpi = vcc->vpi; + tpd->atm_header.gfc = 0; + } + + tpd->spec.length = tx_len; + tpd->spec.nseg = 1; + tpd->spec.aal = fore200e_atm2fore_aal(vcc->qos.aal); + tpd->spec.intr = 1; + + tpd_haddr.size = sizeof(struct tpd) / (1<tpd_dma >> TPD_HADDR_SHIFT; /* shift the address, as we are in a bitfield */ + + *entry->status = STATUS_PENDING; + fore200e->bus->write(*(u32*)&tpd_haddr, (u32 __iomem *)&entry->cp_entry->tpd_haddr); + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + + return 0; +} + + +static int +fore200e_getstats(struct fore200e* fore200e) +{ + struct host_cmdq* cmdq = &fore200e->host_cmdq; + struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ]; + struct stats_opcode opcode; + int ok; + u32 stats_dma_addr; + + if (fore200e->stats == NULL) { + fore200e->stats = kzalloc(sizeof(struct stats), GFP_KERNEL); + if (fore200e->stats == NULL) + return -ENOMEM; + } + + stats_dma_addr = dma_map_single(fore200e->dev, fore200e->stats, + sizeof(struct stats), DMA_FROM_DEVICE); + if (dma_mapping_error(fore200e->dev, stats_dma_addr)) + return -ENOMEM; + + FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD); + + opcode.opcode = OPCODE_GET_STATS; + opcode.pad = 0; + + fore200e->bus->write(stats_dma_addr, &entry->cp_entry->cmd.stats_block.stats_haddr); + + *entry->status = STATUS_PENDING; + + fore200e->bus->write(*(u32*)&opcode, (u32 __iomem *)&entry->cp_entry->cmd.stats_block.opcode); + + ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400); + + *entry->status = STATUS_FREE; + + dma_unmap_single(fore200e->dev, stats_dma_addr, sizeof(struct stats), DMA_FROM_DEVICE); + + if (ok == 0) { + printk(FORE200E "unable to get statistics from device %s\n", fore200e->name); + return -EIO; + } + + return 0; +} + +#if 0 /* currently unused */ +static int +fore200e_get_oc3(struct fore200e* fore200e, struct oc3_regs* regs) +{ + struct host_cmdq* cmdq = &fore200e->host_cmdq; + struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ]; + struct oc3_opcode opcode; + int ok; + u32 oc3_regs_dma_addr; + + oc3_regs_dma_addr = fore200e->bus->dma_map(fore200e, regs, sizeof(struct oc3_regs), DMA_FROM_DEVICE); + + FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD); + + opcode.opcode = OPCODE_GET_OC3; + opcode.reg = 0; + opcode.value = 0; + opcode.mask = 0; + + fore200e->bus->write(oc3_regs_dma_addr, &entry->cp_entry->cmd.oc3_block.regs_haddr); + + *entry->status = STATUS_PENDING; + + fore200e->bus->write(*(u32*)&opcode, (u32*)&entry->cp_entry->cmd.oc3_block.opcode); + + ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400); + + *entry->status = STATUS_FREE; + + fore200e->bus->dma_unmap(fore200e, oc3_regs_dma_addr, sizeof(struct oc3_regs), DMA_FROM_DEVICE); + + if (ok == 0) { + printk(FORE200E "unable to get OC-3 regs of device %s\n", fore200e->name); + return -EIO; + } + + return 0; +} +#endif + + +static int +fore200e_set_oc3(struct fore200e* fore200e, u32 reg, u32 value, u32 mask) +{ + struct host_cmdq* cmdq = &fore200e->host_cmdq; + struct host_cmdq_entry* entry = &cmdq->host_entry[ cmdq->head ]; + struct oc3_opcode opcode; + int ok; + + DPRINTK(2, "set OC-3 reg = 0x%02x, value = 0x%02x, mask = 0x%02x\n", reg, value, mask); + + FORE200E_NEXT_ENTRY(cmdq->head, QUEUE_SIZE_CMD); + + opcode.opcode = OPCODE_SET_OC3; + opcode.reg = reg; + opcode.value = value; + opcode.mask = mask; + + fore200e->bus->write(0, &entry->cp_entry->cmd.oc3_block.regs_haddr); + + *entry->status = STATUS_PENDING; + + fore200e->bus->write(*(u32*)&opcode, (u32 __iomem *)&entry->cp_entry->cmd.oc3_block.opcode); + + ok = fore200e_poll(fore200e, entry->status, STATUS_COMPLETE, 400); + + *entry->status = STATUS_FREE; + + if (ok == 0) { + printk(FORE200E "unable to set OC-3 reg 0x%02x of device %s\n", reg, fore200e->name); + return -EIO; + } + + return 0; +} + + +static int +fore200e_setloop(struct fore200e* fore200e, int loop_mode) +{ + u32 mct_value, mct_mask; + int error; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + switch (loop_mode) { + + case ATM_LM_NONE: + mct_value = 0; + mct_mask = SUNI_MCT_DLE | SUNI_MCT_LLE; + break; + + case ATM_LM_LOC_PHY: + mct_value = mct_mask = SUNI_MCT_DLE; + break; + + case ATM_LM_RMT_PHY: + mct_value = mct_mask = SUNI_MCT_LLE; + break; + + default: + return -EINVAL; + } + + error = fore200e_set_oc3(fore200e, SUNI_MCT, mct_value, mct_mask); + if (error == 0) + fore200e->loop_mode = loop_mode; + + return error; +} + + +static int +fore200e_fetch_stats(struct fore200e* fore200e, struct sonet_stats __user *arg) +{ + struct sonet_stats tmp; + + if (fore200e_getstats(fore200e) < 0) + return -EIO; + + tmp.section_bip = be32_to_cpu(fore200e->stats->oc3.section_bip8_errors); + tmp.line_bip = be32_to_cpu(fore200e->stats->oc3.line_bip24_errors); + tmp.path_bip = be32_to_cpu(fore200e->stats->oc3.path_bip8_errors); + tmp.line_febe = be32_to_cpu(fore200e->stats->oc3.line_febe_errors); + tmp.path_febe = be32_to_cpu(fore200e->stats->oc3.path_febe_errors); + tmp.corr_hcs = be32_to_cpu(fore200e->stats->oc3.corr_hcs_errors); + tmp.uncorr_hcs = be32_to_cpu(fore200e->stats->oc3.ucorr_hcs_errors); + tmp.tx_cells = be32_to_cpu(fore200e->stats->aal0.cells_transmitted) + + be32_to_cpu(fore200e->stats->aal34.cells_transmitted) + + be32_to_cpu(fore200e->stats->aal5.cells_transmitted); + tmp.rx_cells = be32_to_cpu(fore200e->stats->aal0.cells_received) + + be32_to_cpu(fore200e->stats->aal34.cells_received) + + be32_to_cpu(fore200e->stats->aal5.cells_received); + + if (arg) + return copy_to_user(arg, &tmp, sizeof(struct sonet_stats)) ? -EFAULT : 0; + + return 0; +} + + +static int +fore200e_ioctl(struct atm_dev* dev, unsigned int cmd, void __user * arg) +{ + struct fore200e* fore200e = FORE200E_DEV(dev); + + DPRINTK(2, "ioctl cmd = 0x%x (%u), arg = 0x%p (%lu)\n", cmd, cmd, arg, (unsigned long)arg); + + switch (cmd) { + + case SONET_GETSTAT: + return fore200e_fetch_stats(fore200e, (struct sonet_stats __user *)arg); + + case SONET_GETDIAG: + return put_user(0, (int __user *)arg) ? -EFAULT : 0; + + case ATM_SETLOOP: + return fore200e_setloop(fore200e, (int)(unsigned long)arg); + + case ATM_GETLOOP: + return put_user(fore200e->loop_mode, (int __user *)arg) ? -EFAULT : 0; + + case ATM_QUERYLOOP: + return put_user(ATM_LM_LOC_PHY | ATM_LM_RMT_PHY, (int __user *)arg) ? -EFAULT : 0; + } + + return -ENOSYS; /* not implemented */ +} + + +static int +fore200e_change_qos(struct atm_vcc* vcc,struct atm_qos* qos, int flags) +{ + struct fore200e_vcc* fore200e_vcc = FORE200E_VCC(vcc); + struct fore200e* fore200e = FORE200E_DEV(vcc->dev); + + if (!test_bit(ATM_VF_READY, &vcc->flags)) { + DPRINTK(1, "VC %d.%d.%d not ready for QoS change\n", vcc->itf, vcc->vpi, vcc->vpi); + return -EINVAL; + } + + DPRINTK(2, "change_qos %d.%d.%d, " + "(tx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d; " + "rx: cl=%s, pcr=%d-%d, cdv=%d, max_sdu=%d), flags = 0x%x\n" + "available_cell_rate = %u", + vcc->itf, vcc->vpi, vcc->vci, + fore200e_traffic_class[ qos->txtp.traffic_class ], + qos->txtp.min_pcr, qos->txtp.max_pcr, qos->txtp.max_cdv, qos->txtp.max_sdu, + fore200e_traffic_class[ qos->rxtp.traffic_class ], + qos->rxtp.min_pcr, qos->rxtp.max_pcr, qos->rxtp.max_cdv, qos->rxtp.max_sdu, + flags, fore200e->available_cell_rate); + + if ((qos->txtp.traffic_class == ATM_CBR) && (qos->txtp.max_pcr > 0)) { + + mutex_lock(&fore200e->rate_mtx); + if (fore200e->available_cell_rate + vcc->qos.txtp.max_pcr < qos->txtp.max_pcr) { + mutex_unlock(&fore200e->rate_mtx); + return -EAGAIN; + } + + fore200e->available_cell_rate += vcc->qos.txtp.max_pcr; + fore200e->available_cell_rate -= qos->txtp.max_pcr; + + mutex_unlock(&fore200e->rate_mtx); + + memcpy(&vcc->qos, qos, sizeof(struct atm_qos)); + + /* update rate control parameters */ + fore200e_rate_ctrl(qos, &fore200e_vcc->rate); + + set_bit(ATM_VF_HASQOS, &vcc->flags); + + return 0; + } + + return -EINVAL; +} + + +static int fore200e_irq_request(struct fore200e *fore200e) +{ + if (request_irq(fore200e->irq, fore200e_interrupt, IRQF_SHARED, fore200e->name, fore200e->atm_dev) < 0) { + + printk(FORE200E "unable to reserve IRQ %s for device %s\n", + fore200e_irq_itoa(fore200e->irq), fore200e->name); + return -EBUSY; + } + + printk(FORE200E "IRQ %s reserved for device %s\n", + fore200e_irq_itoa(fore200e->irq), fore200e->name); + +#ifdef FORE200E_USE_TASKLET + tasklet_init(&fore200e->tx_tasklet, fore200e_tx_tasklet, (unsigned long)fore200e); + tasklet_init(&fore200e->rx_tasklet, fore200e_rx_tasklet, (unsigned long)fore200e); +#endif + + fore200e->state = FORE200E_STATE_IRQ; + return 0; +} + + +static int fore200e_get_esi(struct fore200e *fore200e) +{ + struct prom_data* prom = kzalloc(sizeof(struct prom_data), GFP_KERNEL); + int ok, i; + + if (!prom) + return -ENOMEM; + + ok = fore200e->bus->prom_read(fore200e, prom); + if (ok < 0) { + kfree(prom); + return -EBUSY; + } + + printk(FORE200E "device %s, rev. %c, S/N: %d, ESI: %pM\n", + fore200e->name, + (prom->hw_revision & 0xFF) + '@', /* probably meaningless with SBA boards */ + prom->serial_number & 0xFFFF, &prom->mac_addr[2]); + + for (i = 0; i < ESI_LEN; i++) { + fore200e->esi[ i ] = fore200e->atm_dev->esi[ i ] = prom->mac_addr[ i + 2 ]; + } + + kfree(prom); + + return 0; +} + + +static int fore200e_alloc_rx_buf(struct fore200e *fore200e) +{ + int scheme, magn, nbr, size, i; + + struct host_bsq* bsq; + struct buffer* buffer; + + for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) { + for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) { + + bsq = &fore200e->host_bsq[ scheme ][ magn ]; + + nbr = fore200e_rx_buf_nbr[ scheme ][ magn ]; + size = fore200e_rx_buf_size[ scheme ][ magn ]; + + DPRINTK(2, "rx buffers %d / %d are being allocated\n", scheme, magn); + + /* allocate the array of receive buffers */ + buffer = bsq->buffer = kcalloc(nbr, sizeof(struct buffer), + GFP_KERNEL); + + if (buffer == NULL) + return -ENOMEM; + + bsq->freebuf = NULL; + + for (i = 0; i < nbr; i++) { + + buffer[ i ].scheme = scheme; + buffer[ i ].magn = magn; +#ifdef FORE200E_BSQ_DEBUG + buffer[ i ].index = i; + buffer[ i ].supplied = 0; +#endif + + /* allocate the receive buffer body */ + if (fore200e_chunk_alloc(fore200e, + &buffer[ i ].data, size, fore200e->bus->buffer_alignment, + DMA_FROM_DEVICE) < 0) { + + while (i > 0) + fore200e_chunk_free(fore200e, &buffer[ --i ].data); + kfree(buffer); + + return -ENOMEM; + } + + /* insert the buffer into the free buffer list */ + buffer[ i ].next = bsq->freebuf; + bsq->freebuf = &buffer[ i ]; + } + /* all the buffers are free, initially */ + bsq->freebuf_count = nbr; + +#ifdef FORE200E_BSQ_DEBUG + bsq_audit(3, bsq, scheme, magn); +#endif + } + } + + fore200e->state = FORE200E_STATE_ALLOC_BUF; + return 0; +} + + +static int fore200e_init_bs_queue(struct fore200e *fore200e) +{ + int scheme, magn, i; + + struct host_bsq* bsq; + struct cp_bsq_entry __iomem * cp_entry; + + for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) { + for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) { + + DPRINTK(2, "buffer supply queue %d / %d is being initialized\n", scheme, magn); + + bsq = &fore200e->host_bsq[ scheme ][ magn ]; + + /* allocate and align the array of status words */ + if (fore200e_dma_chunk_alloc(fore200e, + &bsq->status, + sizeof(enum status), + QUEUE_SIZE_BS, + fore200e->bus->status_alignment) < 0) { + return -ENOMEM; + } + + /* allocate and align the array of receive buffer descriptors */ + if (fore200e_dma_chunk_alloc(fore200e, + &bsq->rbd_block, + sizeof(struct rbd_block), + QUEUE_SIZE_BS, + fore200e->bus->descr_alignment) < 0) { + + fore200e_dma_chunk_free(fore200e, &bsq->status); + return -ENOMEM; + } + + /* get the base address of the cp resident buffer supply queue entries */ + cp_entry = fore200e->virt_base + + fore200e->bus->read(&fore200e->cp_queues->cp_bsq[ scheme ][ magn ]); + + /* fill the host resident and cp resident buffer supply queue entries */ + for (i = 0; i < QUEUE_SIZE_BS; i++) { + + bsq->host_entry[ i ].status = + FORE200E_INDEX(bsq->status.align_addr, enum status, i); + bsq->host_entry[ i ].rbd_block = + FORE200E_INDEX(bsq->rbd_block.align_addr, struct rbd_block, i); + bsq->host_entry[ i ].rbd_block_dma = + FORE200E_DMA_INDEX(bsq->rbd_block.dma_addr, struct rbd_block, i); + bsq->host_entry[ i ].cp_entry = &cp_entry[ i ]; + + *bsq->host_entry[ i ].status = STATUS_FREE; + + fore200e->bus->write(FORE200E_DMA_INDEX(bsq->status.dma_addr, enum status, i), + &cp_entry[ i ].status_haddr); + } + } + } + + fore200e->state = FORE200E_STATE_INIT_BSQ; + return 0; +} + + +static int fore200e_init_rx_queue(struct fore200e *fore200e) +{ + struct host_rxq* rxq = &fore200e->host_rxq; + struct cp_rxq_entry __iomem * cp_entry; + int i; + + DPRINTK(2, "receive queue is being initialized\n"); + + /* allocate and align the array of status words */ + if (fore200e_dma_chunk_alloc(fore200e, + &rxq->status, + sizeof(enum status), + QUEUE_SIZE_RX, + fore200e->bus->status_alignment) < 0) { + return -ENOMEM; + } + + /* allocate and align the array of receive PDU descriptors */ + if (fore200e_dma_chunk_alloc(fore200e, + &rxq->rpd, + sizeof(struct rpd), + QUEUE_SIZE_RX, + fore200e->bus->descr_alignment) < 0) { + + fore200e_dma_chunk_free(fore200e, &rxq->status); + return -ENOMEM; + } + + /* get the base address of the cp resident rx queue entries */ + cp_entry = fore200e->virt_base + fore200e->bus->read(&fore200e->cp_queues->cp_rxq); + + /* fill the host resident and cp resident rx entries */ + for (i=0; i < QUEUE_SIZE_RX; i++) { + + rxq->host_entry[ i ].status = + FORE200E_INDEX(rxq->status.align_addr, enum status, i); + rxq->host_entry[ i ].rpd = + FORE200E_INDEX(rxq->rpd.align_addr, struct rpd, i); + rxq->host_entry[ i ].rpd_dma = + FORE200E_DMA_INDEX(rxq->rpd.dma_addr, struct rpd, i); + rxq->host_entry[ i ].cp_entry = &cp_entry[ i ]; + + *rxq->host_entry[ i ].status = STATUS_FREE; + + fore200e->bus->write(FORE200E_DMA_INDEX(rxq->status.dma_addr, enum status, i), + &cp_entry[ i ].status_haddr); + + fore200e->bus->write(FORE200E_DMA_INDEX(rxq->rpd.dma_addr, struct rpd, i), + &cp_entry[ i ].rpd_haddr); + } + + /* set the head entry of the queue */ + rxq->head = 0; + + fore200e->state = FORE200E_STATE_INIT_RXQ; + return 0; +} + + +static int fore200e_init_tx_queue(struct fore200e *fore200e) +{ + struct host_txq* txq = &fore200e->host_txq; + struct cp_txq_entry __iomem * cp_entry; + int i; + + DPRINTK(2, "transmit queue is being initialized\n"); + + /* allocate and align the array of status words */ + if (fore200e_dma_chunk_alloc(fore200e, + &txq->status, + sizeof(enum status), + QUEUE_SIZE_TX, + fore200e->bus->status_alignment) < 0) { + return -ENOMEM; + } + + /* allocate and align the array of transmit PDU descriptors */ + if (fore200e_dma_chunk_alloc(fore200e, + &txq->tpd, + sizeof(struct tpd), + QUEUE_SIZE_TX, + fore200e->bus->descr_alignment) < 0) { + + fore200e_dma_chunk_free(fore200e, &txq->status); + return -ENOMEM; + } + + /* get the base address of the cp resident tx queue entries */ + cp_entry = fore200e->virt_base + fore200e->bus->read(&fore200e->cp_queues->cp_txq); + + /* fill the host resident and cp resident tx entries */ + for (i=0; i < QUEUE_SIZE_TX; i++) { + + txq->host_entry[ i ].status = + FORE200E_INDEX(txq->status.align_addr, enum status, i); + txq->host_entry[ i ].tpd = + FORE200E_INDEX(txq->tpd.align_addr, struct tpd, i); + txq->host_entry[ i ].tpd_dma = + FORE200E_DMA_INDEX(txq->tpd.dma_addr, struct tpd, i); + txq->host_entry[ i ].cp_entry = &cp_entry[ i ]; + + *txq->host_entry[ i ].status = STATUS_FREE; + + fore200e->bus->write(FORE200E_DMA_INDEX(txq->status.dma_addr, enum status, i), + &cp_entry[ i ].status_haddr); + + /* although there is a one-to-one mapping of tx queue entries and tpds, + we do not write here the DMA (physical) base address of each tpd into + the related cp resident entry, because the cp relies on this write + operation to detect that a new pdu has been submitted for tx */ + } + + /* set the head and tail entries of the queue */ + txq->head = 0; + txq->tail = 0; + + fore200e->state = FORE200E_STATE_INIT_TXQ; + return 0; +} + + +static int fore200e_init_cmd_queue(struct fore200e *fore200e) +{ + struct host_cmdq* cmdq = &fore200e->host_cmdq; + struct cp_cmdq_entry __iomem * cp_entry; + int i; + + DPRINTK(2, "command queue is being initialized\n"); + + /* allocate and align the array of status words */ + if (fore200e_dma_chunk_alloc(fore200e, + &cmdq->status, + sizeof(enum status), + QUEUE_SIZE_CMD, + fore200e->bus->status_alignment) < 0) { + return -ENOMEM; + } + + /* get the base address of the cp resident cmd queue entries */ + cp_entry = fore200e->virt_base + fore200e->bus->read(&fore200e->cp_queues->cp_cmdq); + + /* fill the host resident and cp resident cmd entries */ + for (i=0; i < QUEUE_SIZE_CMD; i++) { + + cmdq->host_entry[ i ].status = + FORE200E_INDEX(cmdq->status.align_addr, enum status, i); + cmdq->host_entry[ i ].cp_entry = &cp_entry[ i ]; + + *cmdq->host_entry[ i ].status = STATUS_FREE; + + fore200e->bus->write(FORE200E_DMA_INDEX(cmdq->status.dma_addr, enum status, i), + &cp_entry[ i ].status_haddr); + } + + /* set the head entry of the queue */ + cmdq->head = 0; + + fore200e->state = FORE200E_STATE_INIT_CMDQ; + return 0; +} + + +static void fore200e_param_bs_queue(struct fore200e *fore200e, + enum buffer_scheme scheme, + enum buffer_magn magn, int queue_length, + int pool_size, int supply_blksize) +{ + struct bs_spec __iomem * bs_spec = &fore200e->cp_queues->init.bs_spec[ scheme ][ magn ]; + + fore200e->bus->write(queue_length, &bs_spec->queue_length); + fore200e->bus->write(fore200e_rx_buf_size[ scheme ][ magn ], &bs_spec->buffer_size); + fore200e->bus->write(pool_size, &bs_spec->pool_size); + fore200e->bus->write(supply_blksize, &bs_spec->supply_blksize); +} + + +static int fore200e_initialize(struct fore200e *fore200e) +{ + struct cp_queues __iomem * cpq; + int ok, scheme, magn; + + DPRINTK(2, "device %s being initialized\n", fore200e->name); + + mutex_init(&fore200e->rate_mtx); + spin_lock_init(&fore200e->q_lock); + + cpq = fore200e->cp_queues = fore200e->virt_base + FORE200E_CP_QUEUES_OFFSET; + + /* enable cp to host interrupts */ + fore200e->bus->write(1, &cpq->imask); + + if (fore200e->bus->irq_enable) + fore200e->bus->irq_enable(fore200e); + + fore200e->bus->write(NBR_CONNECT, &cpq->init.num_connect); + + fore200e->bus->write(QUEUE_SIZE_CMD, &cpq->init.cmd_queue_len); + fore200e->bus->write(QUEUE_SIZE_RX, &cpq->init.rx_queue_len); + fore200e->bus->write(QUEUE_SIZE_TX, &cpq->init.tx_queue_len); + + fore200e->bus->write(RSD_EXTENSION, &cpq->init.rsd_extension); + fore200e->bus->write(TSD_EXTENSION, &cpq->init.tsd_extension); + + for (scheme = 0; scheme < BUFFER_SCHEME_NBR; scheme++) + for (magn = 0; magn < BUFFER_MAGN_NBR; magn++) + fore200e_param_bs_queue(fore200e, scheme, magn, + QUEUE_SIZE_BS, + fore200e_rx_buf_nbr[ scheme ][ magn ], + RBD_BLK_SIZE); + + /* issue the initialize command */ + fore200e->bus->write(STATUS_PENDING, &cpq->init.status); + fore200e->bus->write(OPCODE_INITIALIZE, &cpq->init.opcode); + + ok = fore200e_io_poll(fore200e, &cpq->init.status, STATUS_COMPLETE, 3000); + if (ok == 0) { + printk(FORE200E "device %s initialization failed\n", fore200e->name); + return -ENODEV; + } + + printk(FORE200E "device %s initialized\n", fore200e->name); + + fore200e->state = FORE200E_STATE_INITIALIZE; + return 0; +} + + +static void fore200e_monitor_putc(struct fore200e *fore200e, char c) +{ + struct cp_monitor __iomem * monitor = fore200e->cp_monitor; + +#if 0 + printk("%c", c); +#endif + fore200e->bus->write(((u32) c) | FORE200E_CP_MONITOR_UART_AVAIL, &monitor->soft_uart.send); +} + + +static int fore200e_monitor_getc(struct fore200e *fore200e) +{ + struct cp_monitor __iomem * monitor = fore200e->cp_monitor; + unsigned long timeout = jiffies + msecs_to_jiffies(50); + int c; + + while (time_before(jiffies, timeout)) { + + c = (int) fore200e->bus->read(&monitor->soft_uart.recv); + + if (c & FORE200E_CP_MONITOR_UART_AVAIL) { + + fore200e->bus->write(FORE200E_CP_MONITOR_UART_FREE, &monitor->soft_uart.recv); +#if 0 + printk("%c", c & 0xFF); +#endif + return c & 0xFF; + } + } + + return -1; +} + + +static void fore200e_monitor_puts(struct fore200e *fore200e, char *str) +{ + while (*str) { + + /* the i960 monitor doesn't accept any new character if it has something to say */ + while (fore200e_monitor_getc(fore200e) >= 0); + + fore200e_monitor_putc(fore200e, *str++); + } + + while (fore200e_monitor_getc(fore200e) >= 0); +} + +#ifdef __LITTLE_ENDIAN +#define FW_EXT ".bin" +#else +#define FW_EXT "_ecd.bin2" +#endif + +static int fore200e_load_and_start_fw(struct fore200e *fore200e) +{ + const struct firmware *firmware; + const struct fw_header *fw_header; + const __le32 *fw_data; + u32 fw_size; + u32 __iomem *load_addr; + char buf[48]; + int err; + + sprintf(buf, "%s%s", fore200e->bus->proc_name, FW_EXT); + if ((err = request_firmware(&firmware, buf, fore200e->dev)) < 0) { + printk(FORE200E "problem loading firmware image %s\n", fore200e->bus->model_name); + return err; + } + + fw_data = (const __le32 *)firmware->data; + fw_size = firmware->size / sizeof(u32); + fw_header = (const struct fw_header *)firmware->data; + load_addr = fore200e->virt_base + le32_to_cpu(fw_header->load_offset); + + DPRINTK(2, "device %s firmware being loaded at 0x%p (%d words)\n", + fore200e->name, load_addr, fw_size); + + if (le32_to_cpu(fw_header->magic) != FW_HEADER_MAGIC) { + printk(FORE200E "corrupted %s firmware image\n", fore200e->bus->model_name); + goto release; + } + + for (; fw_size--; fw_data++, load_addr++) + fore200e->bus->write(le32_to_cpu(*fw_data), load_addr); + + DPRINTK(2, "device %s firmware being started\n", fore200e->name); + +#if defined(__sparc_v9__) + /* reported to be required by SBA cards on some sparc64 hosts */ + fore200e_spin(100); +#endif + + sprintf(buf, "\rgo %x\r", le32_to_cpu(fw_header->start_offset)); + fore200e_monitor_puts(fore200e, buf); + + if (fore200e_io_poll(fore200e, &fore200e->cp_monitor->bstat, BSTAT_CP_RUNNING, 1000) == 0) { + printk(FORE200E "device %s firmware didn't start\n", fore200e->name); + goto release; + } + + printk(FORE200E "device %s firmware started\n", fore200e->name); + + fore200e->state = FORE200E_STATE_START_FW; + err = 0; + +release: + release_firmware(firmware); + return err; +} + + +static int fore200e_register(struct fore200e *fore200e, struct device *parent) +{ + struct atm_dev* atm_dev; + + DPRINTK(2, "device %s being registered\n", fore200e->name); + + atm_dev = atm_dev_register(fore200e->bus->proc_name, parent, &fore200e_ops, + -1, NULL); + if (atm_dev == NULL) { + printk(FORE200E "unable to register device %s\n", fore200e->name); + return -ENODEV; + } + + atm_dev->dev_data = fore200e; + fore200e->atm_dev = atm_dev; + + atm_dev->ci_range.vpi_bits = FORE200E_VPI_BITS; + atm_dev->ci_range.vci_bits = FORE200E_VCI_BITS; + + fore200e->available_cell_rate = ATM_OC3_PCR; + + fore200e->state = FORE200E_STATE_REGISTER; + return 0; +} + + +static int fore200e_init(struct fore200e *fore200e, struct device *parent) +{ + if (fore200e_register(fore200e, parent) < 0) + return -ENODEV; + + if (fore200e->bus->configure(fore200e) < 0) + return -ENODEV; + + if (fore200e->bus->map(fore200e) < 0) + return -ENODEV; + + if (fore200e_reset(fore200e, 1) < 0) + return -ENODEV; + + if (fore200e_load_and_start_fw(fore200e) < 0) + return -ENODEV; + + if (fore200e_initialize(fore200e) < 0) + return -ENODEV; + + if (fore200e_init_cmd_queue(fore200e) < 0) + return -ENOMEM; + + if (fore200e_init_tx_queue(fore200e) < 0) + return -ENOMEM; + + if (fore200e_init_rx_queue(fore200e) < 0) + return -ENOMEM; + + if (fore200e_init_bs_queue(fore200e) < 0) + return -ENOMEM; + + if (fore200e_alloc_rx_buf(fore200e) < 0) + return -ENOMEM; + + if (fore200e_get_esi(fore200e) < 0) + return -EIO; + + if (fore200e_irq_request(fore200e) < 0) + return -EBUSY; + + fore200e_supply(fore200e); + + /* all done, board initialization is now complete */ + fore200e->state = FORE200E_STATE_COMPLETE; + return 0; +} + +#ifdef CONFIG_SBUS +static const struct of_device_id fore200e_sba_match[]; +static int fore200e_sba_probe(struct platform_device *op) +{ + const struct of_device_id *match; + struct fore200e *fore200e; + static int index = 0; + int err; + + match = of_match_device(fore200e_sba_match, &op->dev); + if (!match) + return -EINVAL; + + fore200e = kzalloc(sizeof(struct fore200e), GFP_KERNEL); + if (!fore200e) + return -ENOMEM; + + fore200e->bus = &fore200e_sbus_ops; + fore200e->dev = &op->dev; + fore200e->irq = op->archdata.irqs[0]; + fore200e->phys_base = op->resource[0].start; + + sprintf(fore200e->name, "SBA-200E-%d", index); + + err = fore200e_init(fore200e, &op->dev); + if (err < 0) { + fore200e_shutdown(fore200e); + kfree(fore200e); + return err; + } + + index++; + dev_set_drvdata(&op->dev, fore200e); + + return 0; +} + +static int fore200e_sba_remove(struct platform_device *op) +{ + struct fore200e *fore200e = dev_get_drvdata(&op->dev); + + fore200e_shutdown(fore200e); + kfree(fore200e); + + return 0; +} + +static const struct of_device_id fore200e_sba_match[] = { + { + .name = SBA200E_PROM_NAME, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, fore200e_sba_match); + +static struct platform_driver fore200e_sba_driver = { + .driver = { + .name = "fore_200e", + .of_match_table = fore200e_sba_match, + }, + .probe = fore200e_sba_probe, + .remove = fore200e_sba_remove, +}; +#endif + +#ifdef CONFIG_PCI +static int fore200e_pca_detect(struct pci_dev *pci_dev, + const struct pci_device_id *pci_ent) +{ + struct fore200e* fore200e; + int err = 0; + static int index = 0; + + if (pci_enable_device(pci_dev)) { + err = -EINVAL; + goto out; + } + + if (dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32))) { + err = -EINVAL; + goto out; + } + + fore200e = kzalloc(sizeof(struct fore200e), GFP_KERNEL); + if (fore200e == NULL) { + err = -ENOMEM; + goto out_disable; + } + + fore200e->bus = &fore200e_pci_ops; + fore200e->dev = &pci_dev->dev; + fore200e->irq = pci_dev->irq; + fore200e->phys_base = pci_resource_start(pci_dev, 0); + + sprintf(fore200e->name, "PCA-200E-%d", index - 1); + + pci_set_master(pci_dev); + + printk(FORE200E "device PCA-200E found at 0x%lx, IRQ %s\n", + fore200e->phys_base, fore200e_irq_itoa(fore200e->irq)); + + sprintf(fore200e->name, "PCA-200E-%d", index); + + err = fore200e_init(fore200e, &pci_dev->dev); + if (err < 0) { + fore200e_shutdown(fore200e); + goto out_free; + } + + ++index; + pci_set_drvdata(pci_dev, fore200e); + +out: + return err; + +out_free: + kfree(fore200e); +out_disable: + pci_disable_device(pci_dev); + goto out; +} + + +static void fore200e_pca_remove_one(struct pci_dev *pci_dev) +{ + struct fore200e *fore200e; + + fore200e = pci_get_drvdata(pci_dev); + + fore200e_shutdown(fore200e); + kfree(fore200e); + pci_disable_device(pci_dev); +} + + +static const struct pci_device_id fore200e_pca_tbl[] = { + { PCI_VENDOR_ID_FORE, PCI_DEVICE_ID_FORE_PCA200E, PCI_ANY_ID, PCI_ANY_ID }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, fore200e_pca_tbl); + +static struct pci_driver fore200e_pca_driver = { + .name = "fore_200e", + .probe = fore200e_pca_detect, + .remove = fore200e_pca_remove_one, + .id_table = fore200e_pca_tbl, +}; +#endif + +static int __init fore200e_module_init(void) +{ + int err = 0; + + printk(FORE200E "FORE Systems 200E-series ATM driver - version " FORE200E_VERSION "\n"); + +#ifdef CONFIG_SBUS + err = platform_driver_register(&fore200e_sba_driver); + if (err) + return err; +#endif + +#ifdef CONFIG_PCI + err = pci_register_driver(&fore200e_pca_driver); +#endif + +#ifdef CONFIG_SBUS + if (err) + platform_driver_unregister(&fore200e_sba_driver); +#endif + + return err; +} + +static void __exit fore200e_module_cleanup(void) +{ +#ifdef CONFIG_PCI + pci_unregister_driver(&fore200e_pca_driver); +#endif +#ifdef CONFIG_SBUS + platform_driver_unregister(&fore200e_sba_driver); +#endif +} + +static int +fore200e_proc_read(struct atm_dev *dev, loff_t* pos, char* page) +{ + struct fore200e* fore200e = FORE200E_DEV(dev); + struct fore200e_vcc* fore200e_vcc; + struct atm_vcc* vcc; + int i, len, left = *pos; + unsigned long flags; + + if (!left--) { + + if (fore200e_getstats(fore200e) < 0) + return -EIO; + + len = sprintf(page,"\n" + " device:\n" + " internal name:\t\t%s\n", fore200e->name); + + /* print bus-specific information */ + if (fore200e->bus->proc_read) + len += fore200e->bus->proc_read(fore200e, page + len); + + len += sprintf(page + len, + " interrupt line:\t\t%s\n" + " physical base address:\t0x%p\n" + " virtual base address:\t0x%p\n" + " factory address (ESI):\t%pM\n" + " board serial number:\t\t%d\n\n", + fore200e_irq_itoa(fore200e->irq), + (void*)fore200e->phys_base, + fore200e->virt_base, + fore200e->esi, + fore200e->esi[4] * 256 + fore200e->esi[5]); + + return len; + } + + if (!left--) + return sprintf(page, + " free small bufs, scheme 1:\t%d\n" + " free large bufs, scheme 1:\t%d\n" + " free small bufs, scheme 2:\t%d\n" + " free large bufs, scheme 2:\t%d\n", + fore200e->host_bsq[ BUFFER_SCHEME_ONE ][ BUFFER_MAGN_SMALL ].freebuf_count, + fore200e->host_bsq[ BUFFER_SCHEME_ONE ][ BUFFER_MAGN_LARGE ].freebuf_count, + fore200e->host_bsq[ BUFFER_SCHEME_TWO ][ BUFFER_MAGN_SMALL ].freebuf_count, + fore200e->host_bsq[ BUFFER_SCHEME_TWO ][ BUFFER_MAGN_LARGE ].freebuf_count); + + if (!left--) { + u32 hb = fore200e->bus->read(&fore200e->cp_queues->heartbeat); + + len = sprintf(page,"\n\n" + " cell processor:\n" + " heartbeat state:\t\t"); + + if (hb >> 16 != 0xDEAD) + len += sprintf(page + len, "0x%08x\n", hb); + else + len += sprintf(page + len, "*** FATAL ERROR %04x ***\n", hb & 0xFFFF); + + return len; + } + + if (!left--) { + static const char* media_name[] = { + "unshielded twisted pair", + "multimode optical fiber ST", + "multimode optical fiber SC", + "single-mode optical fiber ST", + "single-mode optical fiber SC", + "unknown" + }; + + static const char* oc3_mode[] = { + "normal operation", + "diagnostic loopback", + "line loopback", + "unknown" + }; + + u32 fw_release = fore200e->bus->read(&fore200e->cp_queues->fw_release); + u32 mon960_release = fore200e->bus->read(&fore200e->cp_queues->mon960_release); + u32 oc3_revision = fore200e->bus->read(&fore200e->cp_queues->oc3_revision); + u32 media_index = FORE200E_MEDIA_INDEX(fore200e->bus->read(&fore200e->cp_queues->media_type)); + u32 oc3_index; + + if (media_index > 4) + media_index = 5; + + switch (fore200e->loop_mode) { + case ATM_LM_NONE: oc3_index = 0; + break; + case ATM_LM_LOC_PHY: oc3_index = 1; + break; + case ATM_LM_RMT_PHY: oc3_index = 2; + break; + default: oc3_index = 3; + } + + return sprintf(page, + " firmware release:\t\t%d.%d.%d\n" + " monitor release:\t\t%d.%d\n" + " media type:\t\t\t%s\n" + " OC-3 revision:\t\t0x%x\n" + " OC-3 mode:\t\t\t%s", + fw_release >> 16, fw_release << 16 >> 24, fw_release << 24 >> 24, + mon960_release >> 16, mon960_release << 16 >> 16, + media_name[ media_index ], + oc3_revision, + oc3_mode[ oc3_index ]); + } + + if (!left--) { + struct cp_monitor __iomem * cp_monitor = fore200e->cp_monitor; + + return sprintf(page, + "\n\n" + " monitor:\n" + " version number:\t\t%d\n" + " boot status word:\t\t0x%08x\n", + fore200e->bus->read(&cp_monitor->mon_version), + fore200e->bus->read(&cp_monitor->bstat)); + } + + if (!left--) + return sprintf(page, + "\n" + " device statistics:\n" + " 4b5b:\n" + " crc_header_errors:\t\t%10u\n" + " framing_errors:\t\t%10u\n", + be32_to_cpu(fore200e->stats->phy.crc_header_errors), + be32_to_cpu(fore200e->stats->phy.framing_errors)); + + if (!left--) + return sprintf(page, "\n" + " OC-3:\n" + " section_bip8_errors:\t%10u\n" + " path_bip8_errors:\t\t%10u\n" + " line_bip24_errors:\t\t%10u\n" + " line_febe_errors:\t\t%10u\n" + " path_febe_errors:\t\t%10u\n" + " corr_hcs_errors:\t\t%10u\n" + " ucorr_hcs_errors:\t\t%10u\n", + be32_to_cpu(fore200e->stats->oc3.section_bip8_errors), + be32_to_cpu(fore200e->stats->oc3.path_bip8_errors), + be32_to_cpu(fore200e->stats->oc3.line_bip24_errors), + be32_to_cpu(fore200e->stats->oc3.line_febe_errors), + be32_to_cpu(fore200e->stats->oc3.path_febe_errors), + be32_to_cpu(fore200e->stats->oc3.corr_hcs_errors), + be32_to_cpu(fore200e->stats->oc3.ucorr_hcs_errors)); + + if (!left--) + return sprintf(page,"\n" + " ATM:\t\t\t\t cells\n" + " TX:\t\t\t%10u\n" + " RX:\t\t\t%10u\n" + " vpi out of range:\t\t%10u\n" + " vpi no conn:\t\t%10u\n" + " vci out of range:\t\t%10u\n" + " vci no conn:\t\t%10u\n", + be32_to_cpu(fore200e->stats->atm.cells_transmitted), + be32_to_cpu(fore200e->stats->atm.cells_received), + be32_to_cpu(fore200e->stats->atm.vpi_bad_range), + be32_to_cpu(fore200e->stats->atm.vpi_no_conn), + be32_to_cpu(fore200e->stats->atm.vci_bad_range), + be32_to_cpu(fore200e->stats->atm.vci_no_conn)); + + if (!left--) + return sprintf(page,"\n" + " AAL0:\t\t\t cells\n" + " TX:\t\t\t%10u\n" + " RX:\t\t\t%10u\n" + " dropped:\t\t\t%10u\n", + be32_to_cpu(fore200e->stats->aal0.cells_transmitted), + be32_to_cpu(fore200e->stats->aal0.cells_received), + be32_to_cpu(fore200e->stats->aal0.cells_dropped)); + + if (!left--) + return sprintf(page,"\n" + " AAL3/4:\n" + " SAR sublayer:\t\t cells\n" + " TX:\t\t\t%10u\n" + " RX:\t\t\t%10u\n" + " dropped:\t\t\t%10u\n" + " CRC errors:\t\t%10u\n" + " protocol errors:\t\t%10u\n\n" + " CS sublayer:\t\t PDUs\n" + " TX:\t\t\t%10u\n" + " RX:\t\t\t%10u\n" + " dropped:\t\t\t%10u\n" + " protocol errors:\t\t%10u\n", + be32_to_cpu(fore200e->stats->aal34.cells_transmitted), + be32_to_cpu(fore200e->stats->aal34.cells_received), + be32_to_cpu(fore200e->stats->aal34.cells_dropped), + be32_to_cpu(fore200e->stats->aal34.cells_crc_errors), + be32_to_cpu(fore200e->stats->aal34.cells_protocol_errors), + be32_to_cpu(fore200e->stats->aal34.cspdus_transmitted), + be32_to_cpu(fore200e->stats->aal34.cspdus_received), + be32_to_cpu(fore200e->stats->aal34.cspdus_dropped), + be32_to_cpu(fore200e->stats->aal34.cspdus_protocol_errors)); + + if (!left--) + return sprintf(page,"\n" + " AAL5:\n" + " SAR sublayer:\t\t cells\n" + " TX:\t\t\t%10u\n" + " RX:\t\t\t%10u\n" + " dropped:\t\t\t%10u\n" + " congestions:\t\t%10u\n\n" + " CS sublayer:\t\t PDUs\n" + " TX:\t\t\t%10u\n" + " RX:\t\t\t%10u\n" + " dropped:\t\t\t%10u\n" + " CRC errors:\t\t%10u\n" + " protocol errors:\t\t%10u\n", + be32_to_cpu(fore200e->stats->aal5.cells_transmitted), + be32_to_cpu(fore200e->stats->aal5.cells_received), + be32_to_cpu(fore200e->stats->aal5.cells_dropped), + be32_to_cpu(fore200e->stats->aal5.congestion_experienced), + be32_to_cpu(fore200e->stats->aal5.cspdus_transmitted), + be32_to_cpu(fore200e->stats->aal5.cspdus_received), + be32_to_cpu(fore200e->stats->aal5.cspdus_dropped), + be32_to_cpu(fore200e->stats->aal5.cspdus_crc_errors), + be32_to_cpu(fore200e->stats->aal5.cspdus_protocol_errors)); + + if (!left--) + return sprintf(page,"\n" + " AUX:\t\t allocation failures\n" + " small b1:\t\t\t%10u\n" + " large b1:\t\t\t%10u\n" + " small b2:\t\t\t%10u\n" + " large b2:\t\t\t%10u\n" + " RX PDUs:\t\t\t%10u\n" + " TX PDUs:\t\t\t%10lu\n", + be32_to_cpu(fore200e->stats->aux.small_b1_failed), + be32_to_cpu(fore200e->stats->aux.large_b1_failed), + be32_to_cpu(fore200e->stats->aux.small_b2_failed), + be32_to_cpu(fore200e->stats->aux.large_b2_failed), + be32_to_cpu(fore200e->stats->aux.rpd_alloc_failed), + fore200e->tx_sat); + + if (!left--) + return sprintf(page,"\n" + " receive carrier:\t\t\t%s\n", + fore200e->stats->aux.receive_carrier ? "ON" : "OFF!"); + + if (!left--) { + return sprintf(page,"\n" + " VCCs:\n address VPI VCI AAL " + "TX PDUs TX min/max size RX PDUs RX min/max size\n"); + } + + for (i = 0; i < NBR_CONNECT; i++) { + + vcc = fore200e->vc_map[i].vcc; + + if (vcc == NULL) + continue; + + spin_lock_irqsave(&fore200e->q_lock, flags); + + if (vcc && test_bit(ATM_VF_READY, &vcc->flags) && !left--) { + + fore200e_vcc = FORE200E_VCC(vcc); + ASSERT(fore200e_vcc); + + len = sprintf(page, + " %pK %03d %05d %1d %09lu %05d/%05d %09lu %05d/%05d\n", + vcc, + vcc->vpi, vcc->vci, fore200e_atm2fore_aal(vcc->qos.aal), + fore200e_vcc->tx_pdu, + fore200e_vcc->tx_min_pdu > 0xFFFF ? 0 : fore200e_vcc->tx_min_pdu, + fore200e_vcc->tx_max_pdu, + fore200e_vcc->rx_pdu, + fore200e_vcc->rx_min_pdu > 0xFFFF ? 0 : fore200e_vcc->rx_min_pdu, + fore200e_vcc->rx_max_pdu); + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + return len; + } + + spin_unlock_irqrestore(&fore200e->q_lock, flags); + } + + return 0; +} + +module_init(fore200e_module_init); +module_exit(fore200e_module_cleanup); + + +static const struct atmdev_ops fore200e_ops = { + .open = fore200e_open, + .close = fore200e_close, + .ioctl = fore200e_ioctl, + .send = fore200e_send, + .change_qos = fore200e_change_qos, + .proc_read = fore200e_proc_read, + .owner = THIS_MODULE +}; + +MODULE_LICENSE("GPL"); +#ifdef CONFIG_PCI +#ifdef __LITTLE_ENDIAN__ +MODULE_FIRMWARE("pca200e.bin"); +#else +MODULE_FIRMWARE("pca200e_ecd.bin2"); +#endif +#endif /* CONFIG_PCI */ +#ifdef CONFIG_SBUS +MODULE_FIRMWARE("sba200e_ecd.bin2"); +#endif diff --git a/drivers/atm/fore200e.h b/drivers/atm/fore200e.h new file mode 100644 index 000000000..caf0ea6a3 --- /dev/null +++ b/drivers/atm/fore200e.h @@ -0,0 +1,974 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _FORE200E_H +#define _FORE200E_H + +#ifdef __KERNEL__ + +/* rx buffer sizes */ + +#define SMALL_BUFFER_SIZE 384 /* size of small buffers (multiple of 48 (PCA) and 64 (SBA) bytes) */ +#define LARGE_BUFFER_SIZE 4032 /* size of large buffers (multiple of 48 (PCA) and 64 (SBA) bytes) */ + + +#define RBD_BLK_SIZE 32 /* nbr of supplied rx buffers per rbd */ + + +#define MAX_PDU_SIZE 65535 /* maximum PDU size supported by AALs */ + + +#define BUFFER_S1_SIZE SMALL_BUFFER_SIZE /* size of small buffers, scheme 1 */ +#define BUFFER_L1_SIZE LARGE_BUFFER_SIZE /* size of large buffers, scheme 1 */ + +#define BUFFER_S2_SIZE SMALL_BUFFER_SIZE /* size of small buffers, scheme 2 */ +#define BUFFER_L2_SIZE LARGE_BUFFER_SIZE /* size of large buffers, scheme 2 */ + +#define BUFFER_S1_NBR (RBD_BLK_SIZE * 6) +#define BUFFER_L1_NBR (RBD_BLK_SIZE * 4) + +#define BUFFER_S2_NBR (RBD_BLK_SIZE * 6) +#define BUFFER_L2_NBR (RBD_BLK_SIZE * 4) + + +#define QUEUE_SIZE_CMD 16 /* command queue capacity */ +#define QUEUE_SIZE_RX 64 /* receive queue capacity */ +#define QUEUE_SIZE_TX 256 /* transmit queue capacity */ +#define QUEUE_SIZE_BS 32 /* buffer supply queue capacity */ + +#define FORE200E_VPI_BITS 0 +#define FORE200E_VCI_BITS 10 +#define NBR_CONNECT (1 << (FORE200E_VPI_BITS + FORE200E_VCI_BITS)) /* number of connections */ + + +#define TSD_FIXED 2 +#define TSD_EXTENSION 0 +#define TSD_NBR (TSD_FIXED + TSD_EXTENSION) + + +/* the cp starts putting a received PDU into one *small* buffer, + then it uses a number of *large* buffers for the trailing data. + we compute here the total number of receive segment descriptors + required to hold the largest possible PDU */ + +#define RSD_REQUIRED (((MAX_PDU_SIZE - SMALL_BUFFER_SIZE + LARGE_BUFFER_SIZE) / LARGE_BUFFER_SIZE) + 1) + +#define RSD_FIXED 3 + +/* RSD_REQUIRED receive segment descriptors are enough to describe a max-sized PDU, + but we have to keep the size of the receive PDU descriptor multiple of 32 bytes, + so we add one extra RSD to RSD_EXTENSION + (WARNING: THIS MAY CHANGE IF BUFFER SIZES ARE MODIFIED) */ + +#define RSD_EXTENSION ((RSD_REQUIRED - RSD_FIXED) + 1) +#define RSD_NBR (RSD_FIXED + RSD_EXTENSION) + + +#define FORE200E_DEV(d) ((struct fore200e*)((d)->dev_data)) +#define FORE200E_VCC(d) ((struct fore200e_vcc*)((d)->dev_data)) + +/* bitfields endian games */ + +#if defined(__LITTLE_ENDIAN_BITFIELD) +#define BITFIELD2(b1, b2) b1; b2; +#define BITFIELD3(b1, b2, b3) b1; b2; b3; +#define BITFIELD4(b1, b2, b3, b4) b1; b2; b3; b4; +#define BITFIELD5(b1, b2, b3, b4, b5) b1; b2; b3; b4; b5; +#define BITFIELD6(b1, b2, b3, b4, b5, b6) b1; b2; b3; b4; b5; b6; +#elif defined(__BIG_ENDIAN_BITFIELD) +#define BITFIELD2(b1, b2) b2; b1; +#define BITFIELD3(b1, b2, b3) b3; b2; b1; +#define BITFIELD4(b1, b2, b3, b4) b4; b3; b2; b1; +#define BITFIELD5(b1, b2, b3, b4, b5) b5; b4; b3; b2; b1; +#define BITFIELD6(b1, b2, b3, b4, b5, b6) b6; b5; b4; b3; b2; b1; +#else +#error unknown bitfield endianess +#endif + + +/* ATM cell header (minus HEC byte) */ + +typedef struct atm_header { + BITFIELD5( + u32 clp : 1, /* cell loss priority */ + u32 plt : 3, /* payload type */ + u32 vci : 16, /* virtual channel identifier */ + u32 vpi : 8, /* virtual path identifier */ + u32 gfc : 4 /* generic flow control */ + ) +} atm_header_t; + + +/* ATM adaptation layer id */ + +typedef enum fore200e_aal { + FORE200E_AAL0 = 0, + FORE200E_AAL34 = 4, + FORE200E_AAL5 = 5, +} fore200e_aal_t; + + +/* transmit PDU descriptor specification */ + +typedef struct tpd_spec { + BITFIELD4( + u32 length : 16, /* total PDU length */ + u32 nseg : 8, /* number of transmit segments */ + enum fore200e_aal aal : 4, /* adaptation layer */ + u32 intr : 4 /* interrupt requested */ + ) +} tpd_spec_t; + + +/* transmit PDU rate control */ + +typedef struct tpd_rate +{ + BITFIELD2( + u32 idle_cells : 16, /* number of idle cells to insert */ + u32 data_cells : 16 /* number of data cells to transmit */ + ) +} tpd_rate_t; + + +/* transmit segment descriptor */ + +typedef struct tsd { + u32 buffer; /* transmit buffer DMA address */ + u32 length; /* number of bytes in buffer */ +} tsd_t; + + +/* transmit PDU descriptor */ + +typedef struct tpd { + struct atm_header atm_header; /* ATM header minus HEC byte */ + struct tpd_spec spec; /* tpd specification */ + struct tpd_rate rate; /* tpd rate control */ + u32 pad; /* reserved */ + struct tsd tsd[ TSD_NBR ]; /* transmit segment descriptors */ +} tpd_t; + + +/* receive segment descriptor */ + +typedef struct rsd { + u32 handle; /* host supplied receive buffer handle */ + u32 length; /* number of bytes in buffer */ +} rsd_t; + + +/* receive PDU descriptor */ + +typedef struct rpd { + struct atm_header atm_header; /* ATM header minus HEC byte */ + u32 nseg; /* number of receive segments */ + struct rsd rsd[ RSD_NBR ]; /* receive segment descriptors */ +} rpd_t; + + +/* buffer scheme */ + +typedef enum buffer_scheme { + BUFFER_SCHEME_ONE, + BUFFER_SCHEME_TWO, + BUFFER_SCHEME_NBR /* always last */ +} buffer_scheme_t; + + +/* buffer magnitude */ + +typedef enum buffer_magn { + BUFFER_MAGN_SMALL, + BUFFER_MAGN_LARGE, + BUFFER_MAGN_NBR /* always last */ +} buffer_magn_t; + + +/* receive buffer descriptor */ + +typedef struct rbd { + u32 handle; /* host supplied handle */ + u32 buffer_haddr; /* host DMA address of host buffer */ +} rbd_t; + + +/* receive buffer descriptor block */ + +typedef struct rbd_block { + struct rbd rbd[ RBD_BLK_SIZE ]; /* receive buffer descriptor */ +} rbd_block_t; + + +/* tpd DMA address */ + +typedef struct tpd_haddr { + BITFIELD3( + u32 size : 4, /* tpd size expressed in 32 byte blocks */ + u32 pad : 1, /* reserved */ + u32 haddr : 27 /* tpd DMA addr aligned on 32 byte boundary */ + ) +} tpd_haddr_t; + +#define TPD_HADDR_SHIFT 5 /* addr aligned on 32 byte boundary */ + +/* cp resident transmit queue entry */ + +typedef struct cp_txq_entry { + struct tpd_haddr tpd_haddr; /* host DMA address of tpd */ + u32 status_haddr; /* host DMA address of completion status */ +} cp_txq_entry_t; + + +/* cp resident receive queue entry */ + +typedef struct cp_rxq_entry { + u32 rpd_haddr; /* host DMA address of rpd */ + u32 status_haddr; /* host DMA address of completion status */ +} cp_rxq_entry_t; + + +/* cp resident buffer supply queue entry */ + +typedef struct cp_bsq_entry { + u32 rbd_block_haddr; /* host DMA address of rbd block */ + u32 status_haddr; /* host DMA address of completion status */ +} cp_bsq_entry_t; + + +/* completion status */ + +typedef volatile enum status { + STATUS_PENDING = (1<<0), /* initial status (written by host) */ + STATUS_COMPLETE = (1<<1), /* completion status (written by cp) */ + STATUS_FREE = (1<<2), /* initial status (written by host) */ + STATUS_ERROR = (1<<3) /* completion status (written by cp) */ +} status_t; + + +/* cp operation code */ + +typedef enum opcode { + OPCODE_INITIALIZE = 1, /* initialize board */ + OPCODE_ACTIVATE_VCIN, /* activate incoming VCI */ + OPCODE_ACTIVATE_VCOUT, /* activate outgoing VCI */ + OPCODE_DEACTIVATE_VCIN, /* deactivate incoming VCI */ + OPCODE_DEACTIVATE_VCOUT, /* deactivate incoing VCI */ + OPCODE_GET_STATS, /* get board statistics */ + OPCODE_SET_OC3, /* set OC-3 registers */ + OPCODE_GET_OC3, /* get OC-3 registers */ + OPCODE_RESET_STATS, /* reset board statistics */ + OPCODE_GET_PROM, /* get expansion PROM data (PCI specific) */ + OPCODE_SET_VPI_BITS, /* set x bits of those decoded by the + firmware to be low order bits from + the VPI field of the ATM cell header */ + OPCODE_REQUEST_INTR = (1<<7) /* request interrupt */ +} opcode_t; + + +/* virtual path / virtual channel identifiers */ + +typedef struct vpvc { + BITFIELD3( + u32 vci : 16, /* virtual channel identifier */ + u32 vpi : 8, /* virtual path identifier */ + u32 pad : 8 /* reserved */ + ) +} vpvc_t; + + +/* activate VC command opcode */ + +typedef struct activate_opcode { + BITFIELD4( + enum opcode opcode : 8, /* cp opcode */ + enum fore200e_aal aal : 8, /* adaptation layer */ + enum buffer_scheme scheme : 8, /* buffer scheme */ + u32 pad : 8 /* reserved */ + ) +} activate_opcode_t; + + +/* activate VC command block */ + +typedef struct activate_block { + struct activate_opcode opcode; /* activate VC command opcode */ + struct vpvc vpvc; /* VPI/VCI */ + u32 mtu; /* for AAL0 only */ + +} activate_block_t; + + +/* deactivate VC command opcode */ + +typedef struct deactivate_opcode { + BITFIELD2( + enum opcode opcode : 8, /* cp opcode */ + u32 pad : 24 /* reserved */ + ) +} deactivate_opcode_t; + + +/* deactivate VC command block */ + +typedef struct deactivate_block { + struct deactivate_opcode opcode; /* deactivate VC command opcode */ + struct vpvc vpvc; /* VPI/VCI */ +} deactivate_block_t; + + +/* OC-3 registers */ + +typedef struct oc3_regs { + u32 reg[ 128 ]; /* see the PMC Sierra PC5346 S/UNI-155-Lite + Saturn User Network Interface documentation + for a description of the OC-3 chip registers */ +} oc3_regs_t; + + +/* set/get OC-3 regs command opcode */ + +typedef struct oc3_opcode { + BITFIELD4( + enum opcode opcode : 8, /* cp opcode */ + u32 reg : 8, /* register index */ + u32 value : 8, /* register value */ + u32 mask : 8 /* register mask that specifies which + bits of the register value field + are significant */ + ) +} oc3_opcode_t; + + +/* set/get OC-3 regs command block */ + +typedef struct oc3_block { + struct oc3_opcode opcode; /* set/get OC-3 regs command opcode */ + u32 regs_haddr; /* host DMA address of OC-3 regs buffer */ +} oc3_block_t; + + +/* physical encoding statistics */ + +typedef struct stats_phy { + __be32 crc_header_errors; /* cells received with bad header CRC */ + __be32 framing_errors; /* cells received with bad framing */ + __be32 pad[ 2 ]; /* i960 padding */ +} stats_phy_t; + + +/* OC-3 statistics */ + +typedef struct stats_oc3 { + __be32 section_bip8_errors; /* section 8 bit interleaved parity */ + __be32 path_bip8_errors; /* path 8 bit interleaved parity */ + __be32 line_bip24_errors; /* line 24 bit interleaved parity */ + __be32 line_febe_errors; /* line far end block errors */ + __be32 path_febe_errors; /* path far end block errors */ + __be32 corr_hcs_errors; /* correctable header check sequence */ + __be32 ucorr_hcs_errors; /* uncorrectable header check sequence */ + __be32 pad[ 1 ]; /* i960 padding */ +} stats_oc3_t; + + +/* ATM statistics */ + +typedef struct stats_atm { + __be32 cells_transmitted; /* cells transmitted */ + __be32 cells_received; /* cells received */ + __be32 vpi_bad_range; /* cell drops: VPI out of range */ + __be32 vpi_no_conn; /* cell drops: no connection for VPI */ + __be32 vci_bad_range; /* cell drops: VCI out of range */ + __be32 vci_no_conn; /* cell drops: no connection for VCI */ + __be32 pad[ 2 ]; /* i960 padding */ +} stats_atm_t; + +/* AAL0 statistics */ + +typedef struct stats_aal0 { + __be32 cells_transmitted; /* cells transmitted */ + __be32 cells_received; /* cells received */ + __be32 cells_dropped; /* cells dropped */ + __be32 pad[ 1 ]; /* i960 padding */ +} stats_aal0_t; + + +/* AAL3/4 statistics */ + +typedef struct stats_aal34 { + __be32 cells_transmitted; /* cells transmitted from segmented PDUs */ + __be32 cells_received; /* cells reassembled into PDUs */ + __be32 cells_crc_errors; /* payload CRC error count */ + __be32 cells_protocol_errors; /* SAR or CS layer protocol errors */ + __be32 cells_dropped; /* cells dropped: partial reassembly */ + __be32 cspdus_transmitted; /* CS PDUs transmitted */ + __be32 cspdus_received; /* CS PDUs received */ + __be32 cspdus_protocol_errors; /* CS layer protocol errors */ + __be32 cspdus_dropped; /* reassembled PDUs drop'd (in cells) */ + __be32 pad[ 3 ]; /* i960 padding */ +} stats_aal34_t; + + +/* AAL5 statistics */ + +typedef struct stats_aal5 { + __be32 cells_transmitted; /* cells transmitted from segmented SDUs */ + __be32 cells_received; /* cells reassembled into SDUs */ + __be32 cells_dropped; /* reassembled PDUs dropped (in cells) */ + __be32 congestion_experienced; /* CRC error and length wrong */ + __be32 cspdus_transmitted; /* CS PDUs transmitted */ + __be32 cspdus_received; /* CS PDUs received */ + __be32 cspdus_crc_errors; /* CS PDUs CRC errors */ + __be32 cspdus_protocol_errors; /* CS layer protocol errors */ + __be32 cspdus_dropped; /* reassembled PDUs dropped */ + __be32 pad[ 3 ]; /* i960 padding */ +} stats_aal5_t; + + +/* auxiliary statistics */ + +typedef struct stats_aux { + __be32 small_b1_failed; /* receive BD allocation failures */ + __be32 large_b1_failed; /* receive BD allocation failures */ + __be32 small_b2_failed; /* receive BD allocation failures */ + __be32 large_b2_failed; /* receive BD allocation failures */ + __be32 rpd_alloc_failed; /* receive PDU allocation failures */ + __be32 receive_carrier; /* no carrier = 0, carrier = 1 */ + __be32 pad[ 2 ]; /* i960 padding */ +} stats_aux_t; + + +/* whole statistics buffer */ + +typedef struct stats { + struct stats_phy phy; /* physical encoding statistics */ + struct stats_oc3 oc3; /* OC-3 statistics */ + struct stats_atm atm; /* ATM statistics */ + struct stats_aal0 aal0; /* AAL0 statistics */ + struct stats_aal34 aal34; /* AAL3/4 statistics */ + struct stats_aal5 aal5; /* AAL5 statistics */ + struct stats_aux aux; /* auxiliary statistics */ +} stats_t; + + +/* get statistics command opcode */ + +typedef struct stats_opcode { + BITFIELD2( + enum opcode opcode : 8, /* cp opcode */ + u32 pad : 24 /* reserved */ + ) +} stats_opcode_t; + + +/* get statistics command block */ + +typedef struct stats_block { + struct stats_opcode opcode; /* get statistics command opcode */ + u32 stats_haddr; /* host DMA address of stats buffer */ +} stats_block_t; + + +/* expansion PROM data (PCI specific) */ + +typedef struct prom_data { + u32 hw_revision; /* hardware revision */ + u32 serial_number; /* board serial number */ + u8 mac_addr[ 8 ]; /* board MAC address */ +} prom_data_t; + + +/* get expansion PROM data command opcode */ + +typedef struct prom_opcode { + BITFIELD2( + enum opcode opcode : 8, /* cp opcode */ + u32 pad : 24 /* reserved */ + ) +} prom_opcode_t; + + +/* get expansion PROM data command block */ + +typedef struct prom_block { + struct prom_opcode opcode; /* get PROM data command opcode */ + u32 prom_haddr; /* host DMA address of PROM buffer */ +} prom_block_t; + + +/* cp command */ + +typedef union cmd { + enum opcode opcode; /* operation code */ + struct activate_block activate_block; /* activate VC */ + struct deactivate_block deactivate_block; /* deactivate VC */ + struct stats_block stats_block; /* get statistics */ + struct prom_block prom_block; /* get expansion PROM data */ + struct oc3_block oc3_block; /* get/set OC-3 registers */ + u32 pad[ 4 ]; /* i960 padding */ +} cmd_t; + + +/* cp resident command queue */ + +typedef struct cp_cmdq_entry { + union cmd cmd; /* command */ + u32 status_haddr; /* host DMA address of completion status */ + u32 pad[ 3 ]; /* i960 padding */ +} cp_cmdq_entry_t; + + +/* host resident transmit queue entry */ + +typedef struct host_txq_entry { + struct cp_txq_entry __iomem *cp_entry; /* addr of cp resident tx queue entry */ + enum status* status; /* addr of host resident status */ + struct tpd* tpd; /* addr of transmit PDU descriptor */ + u32 tpd_dma; /* DMA address of tpd */ + struct sk_buff* skb; /* related skb */ + void* data; /* copy of misaligned data */ + unsigned long incarn; /* vc_map incarnation when submitted for tx */ + struct fore200e_vc_map* vc_map; + +} host_txq_entry_t; + + +/* host resident receive queue entry */ + +typedef struct host_rxq_entry { + struct cp_rxq_entry __iomem *cp_entry; /* addr of cp resident rx queue entry */ + enum status* status; /* addr of host resident status */ + struct rpd* rpd; /* addr of receive PDU descriptor */ + u32 rpd_dma; /* DMA address of rpd */ +} host_rxq_entry_t; + + +/* host resident buffer supply queue entry */ + +typedef struct host_bsq_entry { + struct cp_bsq_entry __iomem *cp_entry; /* addr of cp resident buffer supply queue entry */ + enum status* status; /* addr of host resident status */ + struct rbd_block* rbd_block; /* addr of receive buffer descriptor block */ + u32 rbd_block_dma; /* DMA address od rdb */ +} host_bsq_entry_t; + + +/* host resident command queue entry */ + +typedef struct host_cmdq_entry { + struct cp_cmdq_entry __iomem *cp_entry; /* addr of cp resident cmd queue entry */ + enum status *status; /* addr of host resident status */ +} host_cmdq_entry_t; + + +/* chunk of memory */ + +typedef struct chunk { + void* alloc_addr; /* base address of allocated chunk */ + void* align_addr; /* base address of aligned chunk */ + dma_addr_t dma_addr; /* DMA address of aligned chunk */ + int direction; /* direction of DMA mapping */ + u32 alloc_size; /* length of allocated chunk */ + u32 align_size; /* length of aligned chunk */ +} chunk_t; + +#define dma_size align_size /* DMA useable size */ + + +/* host resident receive buffer */ + +typedef struct buffer { + struct buffer* next; /* next receive buffer */ + enum buffer_scheme scheme; /* buffer scheme */ + enum buffer_magn magn; /* buffer magnitude */ + struct chunk data; /* data buffer */ +#ifdef FORE200E_BSQ_DEBUG + unsigned long index; /* buffer # in queue */ + int supplied; /* 'buffer supplied' flag */ +#endif +} buffer_t; + + +#if (BITS_PER_LONG == 32) +#define FORE200E_BUF2HDL(buffer) ((u32)(buffer)) +#define FORE200E_HDL2BUF(handle) ((struct buffer*)(handle)) +#else /* deal with 64 bit pointers */ +#define FORE200E_BUF2HDL(buffer) ((u32)((u64)(buffer))) +#define FORE200E_HDL2BUF(handle) ((struct buffer*)(((u64)(handle)) | PAGE_OFFSET)) +#endif + + +/* host resident command queue */ + +typedef struct host_cmdq { + struct host_cmdq_entry host_entry[ QUEUE_SIZE_CMD ]; /* host resident cmd queue entries */ + int head; /* head of cmd queue */ + struct chunk status; /* array of completion status */ +} host_cmdq_t; + + +/* host resident transmit queue */ + +typedef struct host_txq { + struct host_txq_entry host_entry[ QUEUE_SIZE_TX ]; /* host resident tx queue entries */ + int head; /* head of tx queue */ + int tail; /* tail of tx queue */ + struct chunk tpd; /* array of tpds */ + struct chunk status; /* arry of completion status */ + int txing; /* number of pending PDUs in tx queue */ +} host_txq_t; + + +/* host resident receive queue */ + +typedef struct host_rxq { + struct host_rxq_entry host_entry[ QUEUE_SIZE_RX ]; /* host resident rx queue entries */ + int head; /* head of rx queue */ + struct chunk rpd; /* array of rpds */ + struct chunk status; /* array of completion status */ +} host_rxq_t; + + +/* host resident buffer supply queues */ + +typedef struct host_bsq { + struct host_bsq_entry host_entry[ QUEUE_SIZE_BS ]; /* host resident buffer supply queue entries */ + int head; /* head of buffer supply queue */ + struct chunk rbd_block; /* array of rbds */ + struct chunk status; /* array of completion status */ + struct buffer* buffer; /* array of rx buffers */ + struct buffer* freebuf; /* list of free rx buffers */ + volatile int freebuf_count; /* count of free rx buffers */ +} host_bsq_t; + + +/* header of the firmware image */ + +typedef struct fw_header { + __le32 magic; /* magic number */ + __le32 version; /* firmware version id */ + __le32 load_offset; /* fw load offset in board memory */ + __le32 start_offset; /* fw execution start address in board memory */ +} fw_header_t; + +#define FW_HEADER_MAGIC 0x65726f66 /* 'fore' */ + + +/* receive buffer supply queues scheme specification */ + +typedef struct bs_spec { + u32 queue_length; /* queue capacity */ + u32 buffer_size; /* host buffer size */ + u32 pool_size; /* number of rbds */ + u32 supply_blksize; /* num of rbds in I/O block (multiple + of 4 between 4 and 124 inclusive) */ +} bs_spec_t; + + +/* initialization command block (one-time command, not in cmd queue) */ + +typedef struct init_block { + enum opcode opcode; /* initialize command */ + enum status status; /* related status word */ + u32 receive_threshold; /* not used */ + u32 num_connect; /* ATM connections */ + u32 cmd_queue_len; /* length of command queue */ + u32 tx_queue_len; /* length of transmit queue */ + u32 rx_queue_len; /* length of receive queue */ + u32 rsd_extension; /* number of extra 32 byte blocks */ + u32 tsd_extension; /* number of extra 32 byte blocks */ + u32 conless_vpvc; /* not used */ + u32 pad[ 2 ]; /* force quad alignment */ + struct bs_spec bs_spec[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ]; /* buffer supply queues spec */ +} init_block_t; + + +typedef enum media_type { + MEDIA_TYPE_CAT5_UTP = 0x06, /* unshielded twisted pair */ + MEDIA_TYPE_MM_OC3_ST = 0x16, /* multimode fiber ST */ + MEDIA_TYPE_MM_OC3_SC = 0x26, /* multimode fiber SC */ + MEDIA_TYPE_SM_OC3_ST = 0x36, /* single-mode fiber ST */ + MEDIA_TYPE_SM_OC3_SC = 0x46 /* single-mode fiber SC */ +} media_type_t; + +#define FORE200E_MEDIA_INDEX(media_type) ((media_type)>>4) + + +/* cp resident queues */ + +typedef struct cp_queues { + u32 cp_cmdq; /* command queue */ + u32 cp_txq; /* transmit queue */ + u32 cp_rxq; /* receive queue */ + u32 cp_bsq[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ]; /* buffer supply queues */ + u32 imask; /* 1 enables cp to host interrupts */ + u32 istat; /* 1 for interrupt posted */ + u32 heap_base; /* offset form beginning of ram */ + u32 heap_size; /* space available for queues */ + u32 hlogger; /* non zero for host logging */ + u32 heartbeat; /* cp heartbeat */ + u32 fw_release; /* firmware version */ + u32 mon960_release; /* i960 monitor version */ + u32 tq_plen; /* transmit throughput measurements */ + /* make sure the init block remains on a quad word boundary */ + struct init_block init; /* one time cmd, not in cmd queue */ + enum media_type media_type; /* media type id */ + u32 oc3_revision; /* OC-3 revision number */ +} cp_queues_t; + + +/* boot status */ + +typedef enum boot_status { + BSTAT_COLD_START = (u32) 0xc01dc01d, /* cold start */ + BSTAT_SELFTEST_OK = (u32) 0x02201958, /* self-test ok */ + BSTAT_SELFTEST_FAIL = (u32) 0xadbadbad, /* self-test failed */ + BSTAT_CP_RUNNING = (u32) 0xce11feed, /* cp is running */ + BSTAT_MON_TOO_BIG = (u32) 0x10aded00 /* i960 monitor is too big */ +} boot_status_t; + + +/* software UART */ + +typedef struct soft_uart { + u32 send; /* write register */ + u32 recv; /* read register */ +} soft_uart_t; + +#define FORE200E_CP_MONITOR_UART_FREE 0x00000000 +#define FORE200E_CP_MONITOR_UART_AVAIL 0x01000000 + + +/* i960 monitor */ + +typedef struct cp_monitor { + struct soft_uart soft_uart; /* software UART */ + enum boot_status bstat; /* boot status */ + u32 app_base; /* application base offset */ + u32 mon_version; /* i960 monitor version */ +} cp_monitor_t; + + +/* device state */ + +typedef enum fore200e_state { + FORE200E_STATE_BLANK, /* initial state */ + FORE200E_STATE_REGISTER, /* device registered */ + FORE200E_STATE_CONFIGURE, /* bus interface configured */ + FORE200E_STATE_MAP, /* board space mapped in host memory */ + FORE200E_STATE_RESET, /* board resetted */ + FORE200E_STATE_START_FW, /* firmware started */ + FORE200E_STATE_INITIALIZE, /* initialize command successful */ + FORE200E_STATE_INIT_CMDQ, /* command queue initialized */ + FORE200E_STATE_INIT_TXQ, /* transmit queue initialized */ + FORE200E_STATE_INIT_RXQ, /* receive queue initialized */ + FORE200E_STATE_INIT_BSQ, /* buffer supply queue initialized */ + FORE200E_STATE_ALLOC_BUF, /* receive buffers allocated */ + FORE200E_STATE_IRQ, /* host interrupt requested */ + FORE200E_STATE_COMPLETE /* initialization completed */ +} fore200e_state; + + +/* PCA-200E registers */ + +typedef struct fore200e_pca_regs { + volatile u32 __iomem * hcr; /* address of host control register */ + volatile u32 __iomem * imr; /* address of host interrupt mask register */ + volatile u32 __iomem * psr; /* address of PCI specific register */ +} fore200e_pca_regs_t; + + +/* SBA-200E registers */ + +typedef struct fore200e_sba_regs { + u32 __iomem *hcr; /* address of host control register */ + u32 __iomem *bsr; /* address of burst transfer size register */ + u32 __iomem *isr; /* address of interrupt level selection register */ +} fore200e_sba_regs_t; + + +/* model-specific registers */ + +typedef union fore200e_regs { + struct fore200e_pca_regs pca; /* PCA-200E registers */ + struct fore200e_sba_regs sba; /* SBA-200E registers */ +} fore200e_regs; + + +struct fore200e; + +/* bus-dependent data */ + +typedef struct fore200e_bus { + char* model_name; /* board model name */ + char* proc_name; /* board name under /proc/atm */ + int descr_alignment; /* tpd/rpd/rbd DMA alignment requirement */ + int buffer_alignment; /* rx buffers DMA alignment requirement */ + int status_alignment; /* status words DMA alignment requirement */ + u32 (*read)(volatile u32 __iomem *); + void (*write)(u32, volatile u32 __iomem *); + int (*configure)(struct fore200e*); + int (*map)(struct fore200e*); + void (*reset)(struct fore200e*); + int (*prom_read)(struct fore200e*, struct prom_data*); + void (*unmap)(struct fore200e*); + void (*irq_enable)(struct fore200e*); + int (*irq_check)(struct fore200e*); + void (*irq_ack)(struct fore200e*); + int (*proc_read)(struct fore200e*, char*); +} fore200e_bus_t; + +/* vc mapping */ + +typedef struct fore200e_vc_map { + struct atm_vcc* vcc; /* vcc entry */ + unsigned long incarn; /* vcc incarnation number */ +} fore200e_vc_map_t; + +#define FORE200E_VC_MAP(fore200e, vpi, vci) \ + (& (fore200e)->vc_map[ ((vpi) << FORE200E_VCI_BITS) | (vci) ]) + + +/* per-device data */ + +typedef struct fore200e { + struct list_head entry; /* next device */ + const struct fore200e_bus* bus; /* bus-dependent code and data */ + union fore200e_regs regs; /* bus-dependent registers */ + struct atm_dev* atm_dev; /* ATM device */ + + enum fore200e_state state; /* device state */ + + char name[16]; /* device name */ + struct device *dev; + int irq; /* irq number */ + unsigned long phys_base; /* physical base address */ + void __iomem * virt_base; /* virtual base address */ + + unsigned char esi[ ESI_LEN ]; /* end system identifier */ + + struct cp_monitor __iomem * cp_monitor; /* i960 monitor address */ + struct cp_queues __iomem * cp_queues; /* cp resident queues */ + struct host_cmdq host_cmdq; /* host resident cmd queue */ + struct host_txq host_txq; /* host resident tx queue */ + struct host_rxq host_rxq; /* host resident rx queue */ + /* host resident buffer supply queues */ + struct host_bsq host_bsq[ BUFFER_SCHEME_NBR ][ BUFFER_MAGN_NBR ]; + + u32 available_cell_rate; /* remaining pseudo-CBR bw on link */ + + int loop_mode; /* S/UNI loopback mode */ + + struct stats* stats; /* last snapshot of the stats */ + + struct mutex rate_mtx; /* protects rate reservation ops */ + spinlock_t q_lock; /* protects queue ops */ +#ifdef FORE200E_USE_TASKLET + struct tasklet_struct tx_tasklet; /* performs tx interrupt work */ + struct tasklet_struct rx_tasklet; /* performs rx interrupt work */ +#endif + unsigned long tx_sat; /* tx queue saturation count */ + + unsigned long incarn_count; + struct fore200e_vc_map vc_map[ NBR_CONNECT ]; /* vc mapping */ +} fore200e_t; + + +/* per-vcc data */ + +typedef struct fore200e_vcc { + enum buffer_scheme scheme; /* rx buffer scheme */ + struct tpd_rate rate; /* tx rate control data */ + int rx_min_pdu; /* size of smallest PDU received */ + int rx_max_pdu; /* size of largest PDU received */ + int tx_min_pdu; /* size of smallest PDU transmitted */ + int tx_max_pdu; /* size of largest PDU transmitted */ + unsigned long tx_pdu; /* nbr of tx pdus */ + unsigned long rx_pdu; /* nbr of rx pdus */ +} fore200e_vcc_t; + + + +/* 200E-series common memory layout */ + +#define FORE200E_CP_MONITOR_OFFSET 0x00000400 /* i960 monitor interface */ +#define FORE200E_CP_QUEUES_OFFSET 0x00004d40 /* cp resident queues */ + + +/* PCA-200E memory layout */ + +#define PCA200E_IOSPACE_LENGTH 0x00200000 + +#define PCA200E_HCR_OFFSET 0x00100000 /* board control register */ +#define PCA200E_IMR_OFFSET 0x00100004 /* host IRQ mask register */ +#define PCA200E_PSR_OFFSET 0x00100008 /* PCI specific register */ + + +/* PCA-200E host control register */ + +#define PCA200E_HCR_RESET (1<<0) /* read / write */ +#define PCA200E_HCR_HOLD_LOCK (1<<1) /* read / write */ +#define PCA200E_HCR_I960FAIL (1<<2) /* read */ +#define PCA200E_HCR_INTRB (1<<2) /* write */ +#define PCA200E_HCR_HOLD_ACK (1<<3) /* read */ +#define PCA200E_HCR_INTRA (1<<3) /* write */ +#define PCA200E_HCR_OUTFULL (1<<4) /* read */ +#define PCA200E_HCR_CLRINTR (1<<4) /* write */ +#define PCA200E_HCR_ESPHOLD (1<<5) /* read */ +#define PCA200E_HCR_INFULL (1<<6) /* read */ +#define PCA200E_HCR_TESTMODE (1<<7) /* read */ + + +/* PCA-200E PCI bus interface regs (offsets in PCI config space) */ + +#define PCA200E_PCI_LATENCY 0x40 /* maximum slave latenty */ +#define PCA200E_PCI_MASTER_CTRL 0x41 /* master control */ +#define PCA200E_PCI_THRESHOLD 0x42 /* burst / continuous req threshold */ + +/* PBI master control register */ + +#define PCA200E_CTRL_DIS_CACHE_RD (1<<0) /* disable cache-line reads */ +#define PCA200E_CTRL_DIS_WRT_INVAL (1<<1) /* disable writes and invalidates */ +#define PCA200E_CTRL_2_CACHE_WRT_INVAL (1<<2) /* require 2 cache-lines for writes and invalidates */ +#define PCA200E_CTRL_IGN_LAT_TIMER (1<<3) /* ignore the latency timer */ +#define PCA200E_CTRL_ENA_CONT_REQ_MODE (1<<4) /* enable continuous request mode */ +#define PCA200E_CTRL_LARGE_PCI_BURSTS (1<<5) /* force large PCI bus bursts */ +#define PCA200E_CTRL_CONVERT_ENDIAN (1<<6) /* convert endianess of slave RAM accesses */ + + + +#define SBA200E_PROM_NAME "FORE,sba-200e" /* device name in openprom tree */ + + +/* size of SBA-200E registers */ + +#define SBA200E_HCR_LENGTH 4 +#define SBA200E_BSR_LENGTH 4 +#define SBA200E_ISR_LENGTH 4 +#define SBA200E_RAM_LENGTH 0x40000 + + +/* SBA-200E SBUS burst transfer size register */ + +#define SBA200E_BSR_BURST4 0x04 +#define SBA200E_BSR_BURST8 0x08 +#define SBA200E_BSR_BURST16 0x10 + + +/* SBA-200E host control register */ + +#define SBA200E_HCR_RESET (1<<0) /* read / write (sticky) */ +#define SBA200E_HCR_HOLD_LOCK (1<<1) /* read / write (sticky) */ +#define SBA200E_HCR_I960FAIL (1<<2) /* read */ +#define SBA200E_HCR_I960SETINTR (1<<2) /* write */ +#define SBA200E_HCR_OUTFULL (1<<3) /* read */ +#define SBA200E_HCR_INTR_CLR (1<<3) /* write */ +#define SBA200E_HCR_INTR_ENA (1<<4) /* read / write (sticky) */ +#define SBA200E_HCR_ESPHOLD (1<<5) /* read */ +#define SBA200E_HCR_INFULL (1<<6) /* read */ +#define SBA200E_HCR_TESTMODE (1<<7) /* read */ +#define SBA200E_HCR_INTR_REQ (1<<8) /* read */ + +#define SBA200E_HCR_STICKY (SBA200E_HCR_RESET | SBA200E_HCR_HOLD_LOCK | SBA200E_HCR_INTR_ENA) + + +#endif /* __KERNEL__ */ +#endif /* _FORE200E_H */ diff --git a/drivers/atm/he.c b/drivers/atm/he.c new file mode 100644 index 000000000..17f44abc9 --- /dev/null +++ b/drivers/atm/he.c @@ -0,0 +1,2866 @@ +/* + + he.c + + ForeRunnerHE ATM Adapter driver for ATM on Linux + Copyright (C) 1999-2001 Naval Research Laboratory + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/* + + he.c + + ForeRunnerHE ATM Adapter driver for ATM on Linux + Copyright (C) 1999-2001 Naval Research Laboratory + + Permission to use, copy, modify and distribute this software and its + documentation is hereby granted, provided that both the copyright + notice and this permission notice appear in all copies of the software, + derivative works or modified versions, and any portions thereof, and + that both notices appear in supporting documentation. + + NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND + DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER + RESULTING FROM THE USE OF THIS SOFTWARE. + + This driver was written using the "Programmer's Reference Manual for + ForeRunnerHE(tm)", MANU0361-01 - Rev. A, 08/21/98. + + AUTHORS: + chas williams + eric kinzie + + NOTES: + 4096 supported 'connections' + group 0 is used for all traffic + interrupt queue 0 is used for all interrupts + aal0 support (based on work from ulrich.u.muller@nokia.com) + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#undef USE_SCATTERGATHER +#undef USE_CHECKSUM_HW /* still confused about this */ +/* #undef HE_DEBUG */ + +#include "he.h" +#include "suni.h" +#include + +#define hprintk(fmt,args...) printk(KERN_ERR DEV_LABEL "%d: " fmt, he_dev->number , ##args) + +#ifdef HE_DEBUG +#define HPRINTK(fmt,args...) printk(KERN_DEBUG DEV_LABEL "%d: " fmt, he_dev->number , ##args) +#else /* !HE_DEBUG */ +#define HPRINTK(fmt,args...) do { } while (0) +#endif /* HE_DEBUG */ + +/* declarations */ + +static int he_open(struct atm_vcc *vcc); +static void he_close(struct atm_vcc *vcc); +static int he_send(struct atm_vcc *vcc, struct sk_buff *skb); +static int he_ioctl(struct atm_dev *dev, unsigned int cmd, void __user *arg); +static irqreturn_t he_irq_handler(int irq, void *dev_id); +static void he_tasklet(unsigned long data); +static int he_proc_read(struct atm_dev *dev,loff_t *pos,char *page); +static int he_start(struct atm_dev *dev); +static void he_stop(struct he_dev *dev); +static void he_phy_put(struct atm_dev *, unsigned char, unsigned long); +static unsigned char he_phy_get(struct atm_dev *, unsigned long); + +static u8 read_prom_byte(struct he_dev *he_dev, int addr); + +/* globals */ + +static struct he_dev *he_devs; +static bool disable64; +static short nvpibits = -1; +static short nvcibits = -1; +static short rx_skb_reserve = 16; +static bool irq_coalesce = true; +static bool sdh; + +/* Read from EEPROM = 0000 0011b */ +static unsigned int readtab[] = { + CS_HIGH | CLK_HIGH, + CS_LOW | CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW | SI_HIGH, + CLK_HIGH | SI_HIGH, /* 1 */ + CLK_LOW | SI_HIGH, + CLK_HIGH | SI_HIGH /* 1 */ +}; + +/* Clock to read from/write to the EEPROM */ +static unsigned int clocktab[] = { + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW +}; + +static const struct atmdev_ops he_ops = +{ + .open = he_open, + .close = he_close, + .ioctl = he_ioctl, + .send = he_send, + .phy_put = he_phy_put, + .phy_get = he_phy_get, + .proc_read = he_proc_read, + .owner = THIS_MODULE +}; + +#define he_writel(dev, val, reg) do { writel(val, (dev)->membase + (reg)); wmb(); } while (0) +#define he_readl(dev, reg) readl((dev)->membase + (reg)) + +/* section 2.12 connection memory access */ + +static __inline__ void +he_writel_internal(struct he_dev *he_dev, unsigned val, unsigned addr, + unsigned flags) +{ + he_writel(he_dev, val, CON_DAT); + (void) he_readl(he_dev, CON_DAT); /* flush posted writes */ + he_writel(he_dev, flags | CON_CTL_WRITE | CON_CTL_ADDR(addr), CON_CTL); + while (he_readl(he_dev, CON_CTL) & CON_CTL_BUSY); +} + +#define he_writel_rcm(dev, val, reg) \ + he_writel_internal(dev, val, reg, CON_CTL_RCM) + +#define he_writel_tcm(dev, val, reg) \ + he_writel_internal(dev, val, reg, CON_CTL_TCM) + +#define he_writel_mbox(dev, val, reg) \ + he_writel_internal(dev, val, reg, CON_CTL_MBOX) + +static unsigned +he_readl_internal(struct he_dev *he_dev, unsigned addr, unsigned flags) +{ + he_writel(he_dev, flags | CON_CTL_READ | CON_CTL_ADDR(addr), CON_CTL); + while (he_readl(he_dev, CON_CTL) & CON_CTL_BUSY); + return he_readl(he_dev, CON_DAT); +} + +#define he_readl_rcm(dev, reg) \ + he_readl_internal(dev, reg, CON_CTL_RCM) + +#define he_readl_tcm(dev, reg) \ + he_readl_internal(dev, reg, CON_CTL_TCM) + +#define he_readl_mbox(dev, reg) \ + he_readl_internal(dev, reg, CON_CTL_MBOX) + + +/* figure 2.2 connection id */ + +#define he_mkcid(dev, vpi, vci) (((vpi << (dev)->vcibits) | vci) & 0x1fff) + +/* 2.5.1 per connection transmit state registers */ + +#define he_writel_tsr0(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 0) +#define he_readl_tsr0(dev, cid) \ + he_readl_tcm(dev, CONFIG_TSRA | (cid << 3) | 0) + +#define he_writel_tsr1(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 1) + +#define he_writel_tsr2(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 2) + +#define he_writel_tsr3(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 3) + +#define he_writel_tsr4(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 4) + + /* from page 2-20 + * + * NOTE While the transmit connection is active, bits 23 through 0 + * of this register must not be written by the host. Byte + * enables should be used during normal operation when writing + * the most significant byte. + */ + +#define he_writel_tsr4_upper(dev, val, cid) \ + he_writel_internal(dev, val, CONFIG_TSRA | (cid << 3) | 4, \ + CON_CTL_TCM \ + | CON_BYTE_DISABLE_2 \ + | CON_BYTE_DISABLE_1 \ + | CON_BYTE_DISABLE_0) + +#define he_readl_tsr4(dev, cid) \ + he_readl_tcm(dev, CONFIG_TSRA | (cid << 3) | 4) + +#define he_writel_tsr5(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 5) + +#define he_writel_tsr6(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 6) + +#define he_writel_tsr7(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRA | (cid << 3) | 7) + + +#define he_writel_tsr8(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 0) + +#define he_writel_tsr9(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 1) + +#define he_writel_tsr10(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 2) + +#define he_writel_tsr11(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRB | (cid << 2) | 3) + + +#define he_writel_tsr12(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRC | (cid << 1) | 0) + +#define he_writel_tsr13(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRC | (cid << 1) | 1) + + +#define he_writel_tsr14(dev, val, cid) \ + he_writel_tcm(dev, val, CONFIG_TSRD | cid) + +#define he_writel_tsr14_upper(dev, val, cid) \ + he_writel_internal(dev, val, CONFIG_TSRD | cid, \ + CON_CTL_TCM \ + | CON_BYTE_DISABLE_2 \ + | CON_BYTE_DISABLE_1 \ + | CON_BYTE_DISABLE_0) + +/* 2.7.1 per connection receive state registers */ + +#define he_writel_rsr0(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 0) +#define he_readl_rsr0(dev, cid) \ + he_readl_rcm(dev, 0x00000 | (cid << 3) | 0) + +#define he_writel_rsr1(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 1) + +#define he_writel_rsr2(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 2) + +#define he_writel_rsr3(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 3) + +#define he_writel_rsr4(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 4) + +#define he_writel_rsr5(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 5) + +#define he_writel_rsr6(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 6) + +#define he_writel_rsr7(dev, val, cid) \ + he_writel_rcm(dev, val, 0x00000 | (cid << 3) | 7) + +static __inline__ struct atm_vcc* +__find_vcc(struct he_dev *he_dev, unsigned cid) +{ + struct hlist_head *head; + struct atm_vcc *vcc; + struct sock *s; + short vpi; + int vci; + + vpi = cid >> he_dev->vcibits; + vci = cid & ((1 << he_dev->vcibits) - 1); + head = &vcc_hash[vci & (VCC_HTABLE_SIZE -1)]; + + sk_for_each(s, head) { + vcc = atm_sk(s); + if (vcc->dev == he_dev->atm_dev && + vcc->vci == vci && vcc->vpi == vpi && + vcc->qos.rxtp.traffic_class != ATM_NONE) { + return vcc; + } + } + return NULL; +} + +static int he_init_one(struct pci_dev *pci_dev, + const struct pci_device_id *pci_ent) +{ + struct atm_dev *atm_dev = NULL; + struct he_dev *he_dev = NULL; + int err = 0; + + printk(KERN_INFO "ATM he driver\n"); + + if (pci_enable_device(pci_dev)) + return -EIO; + if (dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32)) != 0) { + printk(KERN_WARNING "he: no suitable dma available\n"); + err = -EIO; + goto init_one_failure; + } + + atm_dev = atm_dev_register(DEV_LABEL, &pci_dev->dev, &he_ops, -1, NULL); + if (!atm_dev) { + err = -ENODEV; + goto init_one_failure; + } + pci_set_drvdata(pci_dev, atm_dev); + + he_dev = kzalloc(sizeof(struct he_dev), + GFP_KERNEL); + if (!he_dev) { + err = -ENOMEM; + goto init_one_failure; + } + he_dev->pci_dev = pci_dev; + he_dev->atm_dev = atm_dev; + he_dev->atm_dev->dev_data = he_dev; + atm_dev->dev_data = he_dev; + he_dev->number = atm_dev->number; + tasklet_init(&he_dev->tasklet, he_tasklet, (unsigned long) he_dev); + spin_lock_init(&he_dev->global_lock); + + if (he_start(atm_dev)) { + he_stop(he_dev); + err = -ENODEV; + goto init_one_failure; + } + he_dev->next = NULL; + if (he_devs) + he_dev->next = he_devs; + he_devs = he_dev; + return 0; + +init_one_failure: + if (atm_dev) + atm_dev_deregister(atm_dev); + kfree(he_dev); + pci_disable_device(pci_dev); + return err; +} + +static void he_remove_one(struct pci_dev *pci_dev) +{ + struct atm_dev *atm_dev; + struct he_dev *he_dev; + + atm_dev = pci_get_drvdata(pci_dev); + he_dev = HE_DEV(atm_dev); + + /* need to remove from he_devs */ + + he_stop(he_dev); + atm_dev_deregister(atm_dev); + kfree(he_dev); + + pci_disable_device(pci_dev); +} + + +static unsigned +rate_to_atmf(unsigned rate) /* cps to atm forum format */ +{ +#define NONZERO (1 << 14) + + unsigned exp = 0; + + if (rate == 0) + return 0; + + rate <<= 9; + while (rate > 0x3ff) { + ++exp; + rate >>= 1; + } + + return (NONZERO | (exp << 9) | (rate & 0x1ff)); +} + +static void he_init_rx_lbfp0(struct he_dev *he_dev) +{ + unsigned i, lbm_offset, lbufd_index, lbuf_addr, lbuf_count; + unsigned lbufs_per_row = he_dev->cells_per_row / he_dev->cells_per_lbuf; + unsigned lbuf_bufsize = he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD; + unsigned row_offset = he_dev->r0_startrow * he_dev->bytes_per_row; + + lbufd_index = 0; + lbm_offset = he_readl(he_dev, RCMLBM_BA); + + he_writel(he_dev, lbufd_index, RLBF0_H); + + for (i = 0, lbuf_count = 0; i < he_dev->r0_numbuffs; ++i) { + lbufd_index += 2; + lbuf_addr = (row_offset + (lbuf_count * lbuf_bufsize)) / 32; + + he_writel_rcm(he_dev, lbuf_addr, lbm_offset); + he_writel_rcm(he_dev, lbufd_index, lbm_offset + 1); + + if (++lbuf_count == lbufs_per_row) { + lbuf_count = 0; + row_offset += he_dev->bytes_per_row; + } + lbm_offset += 4; + } + + he_writel(he_dev, lbufd_index - 2, RLBF0_T); + he_writel(he_dev, he_dev->r0_numbuffs, RLBF0_C); +} + +static void he_init_rx_lbfp1(struct he_dev *he_dev) +{ + unsigned i, lbm_offset, lbufd_index, lbuf_addr, lbuf_count; + unsigned lbufs_per_row = he_dev->cells_per_row / he_dev->cells_per_lbuf; + unsigned lbuf_bufsize = he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD; + unsigned row_offset = he_dev->r1_startrow * he_dev->bytes_per_row; + + lbufd_index = 1; + lbm_offset = he_readl(he_dev, RCMLBM_BA) + (2 * lbufd_index); + + he_writel(he_dev, lbufd_index, RLBF1_H); + + for (i = 0, lbuf_count = 0; i < he_dev->r1_numbuffs; ++i) { + lbufd_index += 2; + lbuf_addr = (row_offset + (lbuf_count * lbuf_bufsize)) / 32; + + he_writel_rcm(he_dev, lbuf_addr, lbm_offset); + he_writel_rcm(he_dev, lbufd_index, lbm_offset + 1); + + if (++lbuf_count == lbufs_per_row) { + lbuf_count = 0; + row_offset += he_dev->bytes_per_row; + } + lbm_offset += 4; + } + + he_writel(he_dev, lbufd_index - 2, RLBF1_T); + he_writel(he_dev, he_dev->r1_numbuffs, RLBF1_C); +} + +static void he_init_tx_lbfp(struct he_dev *he_dev) +{ + unsigned i, lbm_offset, lbufd_index, lbuf_addr, lbuf_count; + unsigned lbufs_per_row = he_dev->cells_per_row / he_dev->cells_per_lbuf; + unsigned lbuf_bufsize = he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD; + unsigned row_offset = he_dev->tx_startrow * he_dev->bytes_per_row; + + lbufd_index = he_dev->r0_numbuffs + he_dev->r1_numbuffs; + lbm_offset = he_readl(he_dev, RCMLBM_BA) + (2 * lbufd_index); + + he_writel(he_dev, lbufd_index, TLBF_H); + + for (i = 0, lbuf_count = 0; i < he_dev->tx_numbuffs; ++i) { + lbufd_index += 1; + lbuf_addr = (row_offset + (lbuf_count * lbuf_bufsize)) / 32; + + he_writel_rcm(he_dev, lbuf_addr, lbm_offset); + he_writel_rcm(he_dev, lbufd_index, lbm_offset + 1); + + if (++lbuf_count == lbufs_per_row) { + lbuf_count = 0; + row_offset += he_dev->bytes_per_row; + } + lbm_offset += 2; + } + + he_writel(he_dev, lbufd_index - 1, TLBF_T); +} + +static int he_init_tpdrq(struct he_dev *he_dev) +{ + he_dev->tpdrq_base = dma_alloc_coherent(&he_dev->pci_dev->dev, + CONFIG_TPDRQ_SIZE * sizeof(struct he_tpdrq), + &he_dev->tpdrq_phys, + GFP_KERNEL); + if (he_dev->tpdrq_base == NULL) { + hprintk("failed to alloc tpdrq\n"); + return -ENOMEM; + } + + he_dev->tpdrq_tail = he_dev->tpdrq_base; + he_dev->tpdrq_head = he_dev->tpdrq_base; + + he_writel(he_dev, he_dev->tpdrq_phys, TPDRQ_B_H); + he_writel(he_dev, 0, TPDRQ_T); + he_writel(he_dev, CONFIG_TPDRQ_SIZE - 1, TPDRQ_S); + + return 0; +} + +static void he_init_cs_block(struct he_dev *he_dev) +{ + unsigned clock, rate, delta; + int reg; + + /* 5.1.7 cs block initialization */ + + for (reg = 0; reg < 0x20; ++reg) + he_writel_mbox(he_dev, 0x0, CS_STTIM0 + reg); + + /* rate grid timer reload values */ + + clock = he_is622(he_dev) ? 66667000 : 50000000; + rate = he_dev->atm_dev->link_rate; + delta = rate / 16 / 2; + + for (reg = 0; reg < 0x10; ++reg) { + /* 2.4 internal transmit function + * + * we initialize the first row in the rate grid. + * values are period (in clock cycles) of timer + */ + unsigned period = clock / rate; + + he_writel_mbox(he_dev, period, CS_TGRLD0 + reg); + rate -= delta; + } + + if (he_is622(he_dev)) { + /* table 5.2 (4 cells per lbuf) */ + he_writel_mbox(he_dev, 0x000800fa, CS_ERTHR0); + he_writel_mbox(he_dev, 0x000c33cb, CS_ERTHR1); + he_writel_mbox(he_dev, 0x0010101b, CS_ERTHR2); + he_writel_mbox(he_dev, 0x00181dac, CS_ERTHR3); + he_writel_mbox(he_dev, 0x00280600, CS_ERTHR4); + + /* table 5.3, 5.4, 5.5, 5.6, 5.7 */ + he_writel_mbox(he_dev, 0x023de8b3, CS_ERCTL0); + he_writel_mbox(he_dev, 0x1801, CS_ERCTL1); + he_writel_mbox(he_dev, 0x68b3, CS_ERCTL2); + he_writel_mbox(he_dev, 0x1280, CS_ERSTAT0); + he_writel_mbox(he_dev, 0x68b3, CS_ERSTAT1); + he_writel_mbox(he_dev, 0x14585, CS_RTFWR); + + he_writel_mbox(he_dev, 0x4680, CS_RTATR); + + /* table 5.8 */ + he_writel_mbox(he_dev, 0x00159ece, CS_TFBSET); + he_writel_mbox(he_dev, 0x68b3, CS_WCRMAX); + he_writel_mbox(he_dev, 0x5eb3, CS_WCRMIN); + he_writel_mbox(he_dev, 0xe8b3, CS_WCRINC); + he_writel_mbox(he_dev, 0xdeb3, CS_WCRDEC); + he_writel_mbox(he_dev, 0x68b3, CS_WCRCEIL); + + /* table 5.9 */ + he_writel_mbox(he_dev, 0x5, CS_OTPPER); + he_writel_mbox(he_dev, 0x14, CS_OTWPER); + } else { + /* table 5.1 (4 cells per lbuf) */ + he_writel_mbox(he_dev, 0x000400ea, CS_ERTHR0); + he_writel_mbox(he_dev, 0x00063388, CS_ERTHR1); + he_writel_mbox(he_dev, 0x00081018, CS_ERTHR2); + he_writel_mbox(he_dev, 0x000c1dac, CS_ERTHR3); + he_writel_mbox(he_dev, 0x0014051a, CS_ERTHR4); + + /* table 5.3, 5.4, 5.5, 5.6, 5.7 */ + he_writel_mbox(he_dev, 0x0235e4b1, CS_ERCTL0); + he_writel_mbox(he_dev, 0x4701, CS_ERCTL1); + he_writel_mbox(he_dev, 0x64b1, CS_ERCTL2); + he_writel_mbox(he_dev, 0x1280, CS_ERSTAT0); + he_writel_mbox(he_dev, 0x64b1, CS_ERSTAT1); + he_writel_mbox(he_dev, 0xf424, CS_RTFWR); + + he_writel_mbox(he_dev, 0x4680, CS_RTATR); + + /* table 5.8 */ + he_writel_mbox(he_dev, 0x000563b7, CS_TFBSET); + he_writel_mbox(he_dev, 0x64b1, CS_WCRMAX); + he_writel_mbox(he_dev, 0x5ab1, CS_WCRMIN); + he_writel_mbox(he_dev, 0xe4b1, CS_WCRINC); + he_writel_mbox(he_dev, 0xdab1, CS_WCRDEC); + he_writel_mbox(he_dev, 0x64b1, CS_WCRCEIL); + + /* table 5.9 */ + he_writel_mbox(he_dev, 0x6, CS_OTPPER); + he_writel_mbox(he_dev, 0x1e, CS_OTWPER); + } + + he_writel_mbox(he_dev, 0x8, CS_OTTLIM); + + for (reg = 0; reg < 0x8; ++reg) + he_writel_mbox(he_dev, 0x0, CS_HGRRT0 + reg); + +} + +static int he_init_cs_block_rcm(struct he_dev *he_dev) +{ + unsigned (*rategrid)[16][16]; + unsigned rate, delta; + int i, j, reg; + + unsigned rate_atmf, exp, man; + unsigned long long rate_cps; + int mult, buf, buf_limit = 4; + + rategrid = kmalloc( sizeof(unsigned) * 16 * 16, GFP_KERNEL); + if (!rategrid) + return -ENOMEM; + + /* initialize rate grid group table */ + + for (reg = 0x0; reg < 0xff; ++reg) + he_writel_rcm(he_dev, 0x0, CONFIG_RCMABR + reg); + + /* initialize rate controller groups */ + + for (reg = 0x100; reg < 0x1ff; ++reg) + he_writel_rcm(he_dev, 0x0, CONFIG_RCMABR + reg); + + /* initialize tNrm lookup table */ + + /* the manual makes reference to a routine in a sample driver + for proper configuration; fortunately, we only need this + in order to support abr connection */ + + /* initialize rate to group table */ + + rate = he_dev->atm_dev->link_rate; + delta = rate / 32; + + /* + * 2.4 transmit internal functions + * + * we construct a copy of the rate grid used by the scheduler + * in order to construct the rate to group table below + */ + + for (j = 0; j < 16; j++) { + (*rategrid)[0][j] = rate; + rate -= delta; + } + + for (i = 1; i < 16; i++) + for (j = 0; j < 16; j++) + if (i > 14) + (*rategrid)[i][j] = (*rategrid)[i - 1][j] / 4; + else + (*rategrid)[i][j] = (*rategrid)[i - 1][j] / 2; + + /* + * 2.4 transmit internal function + * + * this table maps the upper 5 bits of exponent and mantissa + * of the atm forum representation of the rate into an index + * on rate grid + */ + + rate_atmf = 0; + while (rate_atmf < 0x400) { + man = (rate_atmf & 0x1f) << 4; + exp = rate_atmf >> 5; + + /* + instead of '/ 512', use '>> 9' to prevent a call + to divdu3 on x86 platforms + */ + rate_cps = (unsigned long long) (1UL << exp) * (man + 512) >> 9; + + if (rate_cps < 10) + rate_cps = 10; /* 2.2.1 minimum payload rate is 10 cps */ + + for (i = 255; i > 0; i--) + if ((*rategrid)[i/16][i%16] >= rate_cps) + break; /* pick nearest rate instead? */ + + /* + * each table entry is 16 bits: (rate grid index (8 bits) + * and a buffer limit (8 bits) + * there are two table entries in each 32-bit register + */ + +#ifdef notdef + buf = rate_cps * he_dev->tx_numbuffs / + (he_dev->atm_dev->link_rate * 2); +#else + /* this is pretty, but avoids _divdu3 and is mostly correct */ + mult = he_dev->atm_dev->link_rate / ATM_OC3_PCR; + if (rate_cps > (272ULL * mult)) + buf = 4; + else if (rate_cps > (204ULL * mult)) + buf = 3; + else if (rate_cps > (136ULL * mult)) + buf = 2; + else if (rate_cps > (68ULL * mult)) + buf = 1; + else + buf = 0; +#endif + if (buf > buf_limit) + buf = buf_limit; + reg = (reg << 16) | ((i << 8) | buf); + +#define RTGTBL_OFFSET 0x400 + + if (rate_atmf & 0x1) + he_writel_rcm(he_dev, reg, + CONFIG_RCMABR + RTGTBL_OFFSET + (rate_atmf >> 1)); + + ++rate_atmf; + } + + kfree(rategrid); + return 0; +} + +static int he_init_group(struct he_dev *he_dev, int group) +{ + struct he_buff *heb, *next; + dma_addr_t mapping; + int i; + + he_writel(he_dev, 0x0, G0_RBPS_S + (group * 32)); + he_writel(he_dev, 0x0, G0_RBPS_T + (group * 32)); + he_writel(he_dev, 0x0, G0_RBPS_QI + (group * 32)); + he_writel(he_dev, RBP_THRESH(0x1) | RBP_QSIZE(0x0), + G0_RBPS_BS + (group * 32)); + + /* bitmap table */ + he_dev->rbpl_table = kmalloc_array(BITS_TO_LONGS(RBPL_TABLE_SIZE), + sizeof(*he_dev->rbpl_table), + GFP_KERNEL); + if (!he_dev->rbpl_table) { + hprintk("unable to allocate rbpl bitmap table\n"); + return -ENOMEM; + } + bitmap_zero(he_dev->rbpl_table, RBPL_TABLE_SIZE); + + /* rbpl_virt 64-bit pointers */ + he_dev->rbpl_virt = kmalloc_array(RBPL_TABLE_SIZE, + sizeof(*he_dev->rbpl_virt), + GFP_KERNEL); + if (!he_dev->rbpl_virt) { + hprintk("unable to allocate rbpl virt table\n"); + goto out_free_rbpl_table; + } + + /* large buffer pool */ + he_dev->rbpl_pool = dma_pool_create("rbpl", &he_dev->pci_dev->dev, + CONFIG_RBPL_BUFSIZE, 64, 0); + if (he_dev->rbpl_pool == NULL) { + hprintk("unable to create rbpl pool\n"); + goto out_free_rbpl_virt; + } + + he_dev->rbpl_base = dma_alloc_coherent(&he_dev->pci_dev->dev, + CONFIG_RBPL_SIZE * sizeof(struct he_rbp), + &he_dev->rbpl_phys, GFP_KERNEL); + if (he_dev->rbpl_base == NULL) { + hprintk("failed to alloc rbpl_base\n"); + goto out_destroy_rbpl_pool; + } + + INIT_LIST_HEAD(&he_dev->rbpl_outstanding); + + for (i = 0; i < CONFIG_RBPL_SIZE; ++i) { + + heb = dma_pool_alloc(he_dev->rbpl_pool, GFP_KERNEL, &mapping); + if (!heb) + goto out_free_rbpl; + heb->mapping = mapping; + list_add(&heb->entry, &he_dev->rbpl_outstanding); + + set_bit(i, he_dev->rbpl_table); + he_dev->rbpl_virt[i] = heb; + he_dev->rbpl_hint = i + 1; + he_dev->rbpl_base[i].idx = i << RBP_IDX_OFFSET; + he_dev->rbpl_base[i].phys = mapping + offsetof(struct he_buff, data); + } + he_dev->rbpl_tail = &he_dev->rbpl_base[CONFIG_RBPL_SIZE - 1]; + + he_writel(he_dev, he_dev->rbpl_phys, G0_RBPL_S + (group * 32)); + he_writel(he_dev, RBPL_MASK(he_dev->rbpl_tail), + G0_RBPL_T + (group * 32)); + he_writel(he_dev, (CONFIG_RBPL_BUFSIZE - sizeof(struct he_buff))/4, + G0_RBPL_BS + (group * 32)); + he_writel(he_dev, + RBP_THRESH(CONFIG_RBPL_THRESH) | + RBP_QSIZE(CONFIG_RBPL_SIZE - 1) | + RBP_INT_ENB, + G0_RBPL_QI + (group * 32)); + + /* rx buffer ready queue */ + + he_dev->rbrq_base = dma_alloc_coherent(&he_dev->pci_dev->dev, + CONFIG_RBRQ_SIZE * sizeof(struct he_rbrq), + &he_dev->rbrq_phys, GFP_KERNEL); + if (he_dev->rbrq_base == NULL) { + hprintk("failed to allocate rbrq\n"); + goto out_free_rbpl; + } + + he_dev->rbrq_head = he_dev->rbrq_base; + he_writel(he_dev, he_dev->rbrq_phys, G0_RBRQ_ST + (group * 16)); + he_writel(he_dev, 0, G0_RBRQ_H + (group * 16)); + he_writel(he_dev, + RBRQ_THRESH(CONFIG_RBRQ_THRESH) | RBRQ_SIZE(CONFIG_RBRQ_SIZE - 1), + G0_RBRQ_Q + (group * 16)); + if (irq_coalesce) { + hprintk("coalescing interrupts\n"); + he_writel(he_dev, RBRQ_TIME(768) | RBRQ_COUNT(7), + G0_RBRQ_I + (group * 16)); + } else + he_writel(he_dev, RBRQ_TIME(0) | RBRQ_COUNT(1), + G0_RBRQ_I + (group * 16)); + + /* tx buffer ready queue */ + + he_dev->tbrq_base = dma_alloc_coherent(&he_dev->pci_dev->dev, + CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq), + &he_dev->tbrq_phys, GFP_KERNEL); + if (he_dev->tbrq_base == NULL) { + hprintk("failed to allocate tbrq\n"); + goto out_free_rbpq_base; + } + + he_dev->tbrq_head = he_dev->tbrq_base; + + he_writel(he_dev, he_dev->tbrq_phys, G0_TBRQ_B_T + (group * 16)); + he_writel(he_dev, 0, G0_TBRQ_H + (group * 16)); + he_writel(he_dev, CONFIG_TBRQ_SIZE - 1, G0_TBRQ_S + (group * 16)); + he_writel(he_dev, CONFIG_TBRQ_THRESH, G0_TBRQ_THRESH + (group * 16)); + + return 0; + +out_free_rbpq_base: + dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_RBRQ_SIZE * + sizeof(struct he_rbrq), he_dev->rbrq_base, + he_dev->rbrq_phys); +out_free_rbpl: + list_for_each_entry_safe(heb, next, &he_dev->rbpl_outstanding, entry) + dma_pool_free(he_dev->rbpl_pool, heb, heb->mapping); + + dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_RBPL_SIZE * + sizeof(struct he_rbp), he_dev->rbpl_base, + he_dev->rbpl_phys); +out_destroy_rbpl_pool: + dma_pool_destroy(he_dev->rbpl_pool); +out_free_rbpl_virt: + kfree(he_dev->rbpl_virt); +out_free_rbpl_table: + kfree(he_dev->rbpl_table); + + return -ENOMEM; +} + +static int he_init_irq(struct he_dev *he_dev) +{ + int i; + + /* 2.9.3.5 tail offset for each interrupt queue is located after the + end of the interrupt queue */ + + he_dev->irq_base = dma_alloc_coherent(&he_dev->pci_dev->dev, + (CONFIG_IRQ_SIZE + 1) * sizeof(struct he_irq), + &he_dev->irq_phys, GFP_KERNEL); + if (he_dev->irq_base == NULL) { + hprintk("failed to allocate irq\n"); + return -ENOMEM; + } + he_dev->irq_tailoffset = (unsigned *) + &he_dev->irq_base[CONFIG_IRQ_SIZE]; + *he_dev->irq_tailoffset = 0; + he_dev->irq_head = he_dev->irq_base; + he_dev->irq_tail = he_dev->irq_base; + + for (i = 0; i < CONFIG_IRQ_SIZE; ++i) + he_dev->irq_base[i].isw = ITYPE_INVALID; + + he_writel(he_dev, he_dev->irq_phys, IRQ0_BASE); + he_writel(he_dev, + IRQ_SIZE(CONFIG_IRQ_SIZE) | IRQ_THRESH(CONFIG_IRQ_THRESH), + IRQ0_HEAD); + he_writel(he_dev, IRQ_INT_A | IRQ_TYPE_LINE, IRQ0_CNTL); + he_writel(he_dev, 0x0, IRQ0_DATA); + + he_writel(he_dev, 0x0, IRQ1_BASE); + he_writel(he_dev, 0x0, IRQ1_HEAD); + he_writel(he_dev, 0x0, IRQ1_CNTL); + he_writel(he_dev, 0x0, IRQ1_DATA); + + he_writel(he_dev, 0x0, IRQ2_BASE); + he_writel(he_dev, 0x0, IRQ2_HEAD); + he_writel(he_dev, 0x0, IRQ2_CNTL); + he_writel(he_dev, 0x0, IRQ2_DATA); + + he_writel(he_dev, 0x0, IRQ3_BASE); + he_writel(he_dev, 0x0, IRQ3_HEAD); + he_writel(he_dev, 0x0, IRQ3_CNTL); + he_writel(he_dev, 0x0, IRQ3_DATA); + + /* 2.9.3.2 interrupt queue mapping registers */ + + he_writel(he_dev, 0x0, GRP_10_MAP); + he_writel(he_dev, 0x0, GRP_32_MAP); + he_writel(he_dev, 0x0, GRP_54_MAP); + he_writel(he_dev, 0x0, GRP_76_MAP); + + if (request_irq(he_dev->pci_dev->irq, + he_irq_handler, IRQF_SHARED, DEV_LABEL, he_dev)) { + hprintk("irq %d already in use\n", he_dev->pci_dev->irq); + return -EINVAL; + } + + he_dev->irq = he_dev->pci_dev->irq; + + return 0; +} + +static int he_start(struct atm_dev *dev) +{ + struct he_dev *he_dev; + struct pci_dev *pci_dev; + unsigned long membase; + + u16 command; + u32 gen_cntl_0, host_cntl, lb_swap; + u8 cache_size, timer; + + unsigned err; + unsigned int status, reg; + int i, group; + + he_dev = HE_DEV(dev); + pci_dev = he_dev->pci_dev; + + membase = pci_resource_start(pci_dev, 0); + HPRINTK("membase = 0x%lx irq = %d.\n", membase, pci_dev->irq); + + /* + * pci bus controller initialization + */ + + /* 4.3 pci bus controller-specific initialization */ + if (pci_read_config_dword(pci_dev, GEN_CNTL_0, &gen_cntl_0) != 0) { + hprintk("can't read GEN_CNTL_0\n"); + return -EINVAL; + } + gen_cntl_0 |= (MRL_ENB | MRM_ENB | IGNORE_TIMEOUT); + if (pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0) != 0) { + hprintk("can't write GEN_CNTL_0.\n"); + return -EINVAL; + } + + if (pci_read_config_word(pci_dev, PCI_COMMAND, &command) != 0) { + hprintk("can't read PCI_COMMAND.\n"); + return -EINVAL; + } + + command |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE); + if (pci_write_config_word(pci_dev, PCI_COMMAND, command) != 0) { + hprintk("can't enable memory.\n"); + return -EINVAL; + } + + if (pci_read_config_byte(pci_dev, PCI_CACHE_LINE_SIZE, &cache_size)) { + hprintk("can't read cache line size?\n"); + return -EINVAL; + } + + if (cache_size < 16) { + cache_size = 16; + if (pci_write_config_byte(pci_dev, PCI_CACHE_LINE_SIZE, cache_size)) + hprintk("can't set cache line size to %d\n", cache_size); + } + + if (pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &timer)) { + hprintk("can't read latency timer?\n"); + return -EINVAL; + } + + /* from table 3.9 + * + * LAT_TIMER = 1 + AVG_LAT + BURST_SIZE/BUS_SIZE + * + * AVG_LAT: The average first data read/write latency [maximum 16 clock cycles] + * BURST_SIZE: 1536 bytes (read) for 622, 768 bytes (read) for 155 [192 clock cycles] + * + */ +#define LAT_TIMER 209 + if (timer < LAT_TIMER) { + HPRINTK("latency timer was %d, setting to %d\n", timer, LAT_TIMER); + timer = LAT_TIMER; + if (pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, timer)) + hprintk("can't set latency timer to %d\n", timer); + } + + if (!(he_dev->membase = ioremap(membase, HE_REGMAP_SIZE))) { + hprintk("can't set up page mapping\n"); + return -EINVAL; + } + + /* 4.4 card reset */ + he_writel(he_dev, 0x0, RESET_CNTL); + he_writel(he_dev, 0xff, RESET_CNTL); + + msleep(16); /* 16 ms */ + status = he_readl(he_dev, RESET_CNTL); + if ((status & BOARD_RST_STATUS) == 0) { + hprintk("reset failed\n"); + return -EINVAL; + } + + /* 4.5 set bus width */ + host_cntl = he_readl(he_dev, HOST_CNTL); + if (host_cntl & PCI_BUS_SIZE64) + gen_cntl_0 |= ENBL_64; + else + gen_cntl_0 &= ~ENBL_64; + + if (disable64 == 1) { + hprintk("disabling 64-bit pci bus transfers\n"); + gen_cntl_0 &= ~ENBL_64; + } + + if (gen_cntl_0 & ENBL_64) + hprintk("64-bit transfers enabled\n"); + + pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0); + + /* 4.7 read prom contents */ + for (i = 0; i < PROD_ID_LEN; ++i) + he_dev->prod_id[i] = read_prom_byte(he_dev, PROD_ID + i); + + he_dev->media = read_prom_byte(he_dev, MEDIA); + + for (i = 0; i < 6; ++i) + dev->esi[i] = read_prom_byte(he_dev, MAC_ADDR + i); + + hprintk("%s%s, %pM\n", he_dev->prod_id, + he_dev->media & 0x40 ? "SM" : "MM", dev->esi); + he_dev->atm_dev->link_rate = he_is622(he_dev) ? + ATM_OC12_PCR : ATM_OC3_PCR; + + /* 4.6 set host endianess */ + lb_swap = he_readl(he_dev, LB_SWAP); + if (he_is622(he_dev)) + lb_swap &= ~XFER_SIZE; /* 4 cells */ + else + lb_swap |= XFER_SIZE; /* 8 cells */ +#ifdef __BIG_ENDIAN + lb_swap |= DESC_WR_SWAP | INTR_SWAP | BIG_ENDIAN_HOST; +#else + lb_swap &= ~(DESC_WR_SWAP | INTR_SWAP | BIG_ENDIAN_HOST | + DATA_WR_SWAP | DATA_RD_SWAP | DESC_RD_SWAP); +#endif /* __BIG_ENDIAN */ + he_writel(he_dev, lb_swap, LB_SWAP); + + /* 4.8 sdram controller initialization */ + he_writel(he_dev, he_is622(he_dev) ? LB_64_ENB : 0x0, SDRAM_CTL); + + /* 4.9 initialize rnum value */ + lb_swap |= SWAP_RNUM_MAX(0xf); + he_writel(he_dev, lb_swap, LB_SWAP); + + /* 4.10 initialize the interrupt queues */ + if ((err = he_init_irq(he_dev)) != 0) + return err; + + /* 4.11 enable pci bus controller state machines */ + host_cntl |= (OUTFF_ENB | CMDFF_ENB | + QUICK_RD_RETRY | QUICK_WR_RETRY | PERR_INT_ENB); + he_writel(he_dev, host_cntl, HOST_CNTL); + + gen_cntl_0 |= INT_PROC_ENBL|INIT_ENB; + pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0); + + /* + * atm network controller initialization + */ + + /* 5.1.1 generic configuration state */ + + /* + * local (cell) buffer memory map + * + * HE155 HE622 + * + * 0 ____________1023 bytes 0 _______________________2047 bytes + * | | | | | + * | utility | | rx0 | | + * 5|____________| 255|___________________| u | + * 6| | 256| | t | + * | | | | i | + * | rx0 | row | tx | l | + * | | | | i | + * | | 767|___________________| t | + * 517|____________| 768| | y | + * row 518| | | rx1 | | + * | | 1023|___________________|___| + * | | + * | tx | + * | | + * | | + * 1535|____________| + * 1536| | + * | rx1 | + * 2047|____________| + * + */ + + /* total 4096 connections */ + he_dev->vcibits = CONFIG_DEFAULT_VCIBITS; + he_dev->vpibits = CONFIG_DEFAULT_VPIBITS; + + if (nvpibits != -1 && nvcibits != -1 && nvpibits+nvcibits != HE_MAXCIDBITS) { + hprintk("nvpibits + nvcibits != %d\n", HE_MAXCIDBITS); + return -ENODEV; + } + + if (nvpibits != -1) { + he_dev->vpibits = nvpibits; + he_dev->vcibits = HE_MAXCIDBITS - nvpibits; + } + + if (nvcibits != -1) { + he_dev->vcibits = nvcibits; + he_dev->vpibits = HE_MAXCIDBITS - nvcibits; + } + + + if (he_is622(he_dev)) { + he_dev->cells_per_row = 40; + he_dev->bytes_per_row = 2048; + he_dev->r0_numrows = 256; + he_dev->tx_numrows = 512; + he_dev->r1_numrows = 256; + he_dev->r0_startrow = 0; + he_dev->tx_startrow = 256; + he_dev->r1_startrow = 768; + } else { + he_dev->cells_per_row = 20; + he_dev->bytes_per_row = 1024; + he_dev->r0_numrows = 512; + he_dev->tx_numrows = 1018; + he_dev->r1_numrows = 512; + he_dev->r0_startrow = 6; + he_dev->tx_startrow = 518; + he_dev->r1_startrow = 1536; + } + + he_dev->cells_per_lbuf = 4; + he_dev->buffer_limit = 4; + he_dev->r0_numbuffs = he_dev->r0_numrows * + he_dev->cells_per_row / he_dev->cells_per_lbuf; + if (he_dev->r0_numbuffs > 2560) + he_dev->r0_numbuffs = 2560; + + he_dev->r1_numbuffs = he_dev->r1_numrows * + he_dev->cells_per_row / he_dev->cells_per_lbuf; + if (he_dev->r1_numbuffs > 2560) + he_dev->r1_numbuffs = 2560; + + he_dev->tx_numbuffs = he_dev->tx_numrows * + he_dev->cells_per_row / he_dev->cells_per_lbuf; + if (he_dev->tx_numbuffs > 5120) + he_dev->tx_numbuffs = 5120; + + /* 5.1.2 configure hardware dependent registers */ + + he_writel(he_dev, + SLICE_X(0x2) | ARB_RNUM_MAX(0xf) | TH_PRTY(0x3) | + RH_PRTY(0x3) | TL_PRTY(0x2) | RL_PRTY(0x1) | + (he_is622(he_dev) ? BUS_MULTI(0x28) : BUS_MULTI(0x46)) | + (he_is622(he_dev) ? NET_PREF(0x50) : NET_PREF(0x8c)), + LBARB); + + he_writel(he_dev, BANK_ON | + (he_is622(he_dev) ? (REF_RATE(0x384) | WIDE_DATA) : REF_RATE(0x150)), + SDRAMCON); + + he_writel(he_dev, + (he_is622(he_dev) ? RM_BANK_WAIT(1) : RM_BANK_WAIT(0)) | + RM_RW_WAIT(1), RCMCONFIG); + he_writel(he_dev, + (he_is622(he_dev) ? TM_BANK_WAIT(2) : TM_BANK_WAIT(1)) | + TM_RW_WAIT(1), TCMCONFIG); + + he_writel(he_dev, he_dev->cells_per_lbuf * ATM_CELL_PAYLOAD, LB_CONFIG); + + he_writel(he_dev, + (he_is622(he_dev) ? UT_RD_DELAY(8) : UT_RD_DELAY(0)) | + (he_is622(he_dev) ? RC_UT_MODE(0) : RC_UT_MODE(1)) | + RX_VALVP(he_dev->vpibits) | + RX_VALVC(he_dev->vcibits), RC_CONFIG); + + he_writel(he_dev, DRF_THRESH(0x20) | + (he_is622(he_dev) ? TX_UT_MODE(0) : TX_UT_MODE(1)) | + TX_VCI_MASK(he_dev->vcibits) | + LBFREE_CNT(he_dev->tx_numbuffs), TX_CONFIG); + + he_writel(he_dev, 0x0, TXAAL5_PROTO); + + he_writel(he_dev, PHY_INT_ENB | + (he_is622(he_dev) ? PTMR_PRE(67 - 1) : PTMR_PRE(50 - 1)), + RH_CONFIG); + + /* 5.1.3 initialize connection memory */ + + for (i = 0; i < TCM_MEM_SIZE; ++i) + he_writel_tcm(he_dev, 0, i); + + for (i = 0; i < RCM_MEM_SIZE; ++i) + he_writel_rcm(he_dev, 0, i); + + /* + * transmit connection memory map + * + * tx memory + * 0x0 ___________________ + * | | + * | | + * | TSRa | + * | | + * | | + * 0x8000|___________________| + * | | + * | TSRb | + * 0xc000|___________________| + * | | + * | TSRc | + * 0xe000|___________________| + * | TSRd | + * 0xf000|___________________| + * | tmABR | + * 0x10000|___________________| + * | | + * | tmTPD | + * |___________________| + * | | + * .... + * 0x1ffff|___________________| + * + * + */ + + he_writel(he_dev, CONFIG_TSRB, TSRB_BA); + he_writel(he_dev, CONFIG_TSRC, TSRC_BA); + he_writel(he_dev, CONFIG_TSRD, TSRD_BA); + he_writel(he_dev, CONFIG_TMABR, TMABR_BA); + he_writel(he_dev, CONFIG_TPDBA, TPD_BA); + + + /* + * receive connection memory map + * + * 0x0 ___________________ + * | | + * | | + * | RSRa | + * | | + * | | + * 0x8000|___________________| + * | | + * | rx0/1 | + * | LBM | link lists of local + * | tx | buffer memory + * | | + * 0xd000|___________________| + * | | + * | rmABR | + * 0xe000|___________________| + * | | + * | RSRb | + * |___________________| + * | | + * .... + * 0xffff|___________________| + */ + + he_writel(he_dev, 0x08000, RCMLBM_BA); + he_writel(he_dev, 0x0e000, RCMRSRB_BA); + he_writel(he_dev, 0x0d800, RCMABR_BA); + + /* 5.1.4 initialize local buffer free pools linked lists */ + + he_init_rx_lbfp0(he_dev); + he_init_rx_lbfp1(he_dev); + + he_writel(he_dev, 0x0, RLBC_H); + he_writel(he_dev, 0x0, RLBC_T); + he_writel(he_dev, 0x0, RLBC_H2); + + he_writel(he_dev, 512, RXTHRSH); /* 10% of r0+r1 buffers */ + he_writel(he_dev, 256, LITHRSH); /* 5% of r0+r1 buffers */ + + he_init_tx_lbfp(he_dev); + + he_writel(he_dev, he_is622(he_dev) ? 0x104780 : 0x800, UBUFF_BA); + + /* 5.1.5 initialize intermediate receive queues */ + + if (he_is622(he_dev)) { + he_writel(he_dev, 0x000f, G0_INMQ_S); + he_writel(he_dev, 0x200f, G0_INMQ_L); + + he_writel(he_dev, 0x001f, G1_INMQ_S); + he_writel(he_dev, 0x201f, G1_INMQ_L); + + he_writel(he_dev, 0x002f, G2_INMQ_S); + he_writel(he_dev, 0x202f, G2_INMQ_L); + + he_writel(he_dev, 0x003f, G3_INMQ_S); + he_writel(he_dev, 0x203f, G3_INMQ_L); + + he_writel(he_dev, 0x004f, G4_INMQ_S); + he_writel(he_dev, 0x204f, G4_INMQ_L); + + he_writel(he_dev, 0x005f, G5_INMQ_S); + he_writel(he_dev, 0x205f, G5_INMQ_L); + + he_writel(he_dev, 0x006f, G6_INMQ_S); + he_writel(he_dev, 0x206f, G6_INMQ_L); + + he_writel(he_dev, 0x007f, G7_INMQ_S); + he_writel(he_dev, 0x207f, G7_INMQ_L); + } else { + he_writel(he_dev, 0x0000, G0_INMQ_S); + he_writel(he_dev, 0x0008, G0_INMQ_L); + + he_writel(he_dev, 0x0001, G1_INMQ_S); + he_writel(he_dev, 0x0009, G1_INMQ_L); + + he_writel(he_dev, 0x0002, G2_INMQ_S); + he_writel(he_dev, 0x000a, G2_INMQ_L); + + he_writel(he_dev, 0x0003, G3_INMQ_S); + he_writel(he_dev, 0x000b, G3_INMQ_L); + + he_writel(he_dev, 0x0004, G4_INMQ_S); + he_writel(he_dev, 0x000c, G4_INMQ_L); + + he_writel(he_dev, 0x0005, G5_INMQ_S); + he_writel(he_dev, 0x000d, G5_INMQ_L); + + he_writel(he_dev, 0x0006, G6_INMQ_S); + he_writel(he_dev, 0x000e, G6_INMQ_L); + + he_writel(he_dev, 0x0007, G7_INMQ_S); + he_writel(he_dev, 0x000f, G7_INMQ_L); + } + + /* 5.1.6 application tunable parameters */ + + he_writel(he_dev, 0x0, MCC); + he_writel(he_dev, 0x0, OEC); + he_writel(he_dev, 0x0, DCC); + he_writel(he_dev, 0x0, CEC); + + /* 5.1.7 cs block initialization */ + + he_init_cs_block(he_dev); + + /* 5.1.8 cs block connection memory initialization */ + + if (he_init_cs_block_rcm(he_dev) < 0) + return -ENOMEM; + + /* 5.1.10 initialize host structures */ + + he_init_tpdrq(he_dev); + + he_dev->tpd_pool = dma_pool_create("tpd", &he_dev->pci_dev->dev, + sizeof(struct he_tpd), TPD_ALIGNMENT, 0); + if (he_dev->tpd_pool == NULL) { + hprintk("unable to create tpd dma_pool\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&he_dev->outstanding_tpds); + + if (he_init_group(he_dev, 0) != 0) + return -ENOMEM; + + for (group = 1; group < HE_NUM_GROUPS; ++group) { + he_writel(he_dev, 0x0, G0_RBPS_S + (group * 32)); + he_writel(he_dev, 0x0, G0_RBPS_T + (group * 32)); + he_writel(he_dev, 0x0, G0_RBPS_QI + (group * 32)); + he_writel(he_dev, RBP_THRESH(0x1) | RBP_QSIZE(0x0), + G0_RBPS_BS + (group * 32)); + + he_writel(he_dev, 0x0, G0_RBPL_S + (group * 32)); + he_writel(he_dev, 0x0, G0_RBPL_T + (group * 32)); + he_writel(he_dev, RBP_THRESH(0x1) | RBP_QSIZE(0x0), + G0_RBPL_QI + (group * 32)); + he_writel(he_dev, 0x0, G0_RBPL_BS + (group * 32)); + + he_writel(he_dev, 0x0, G0_RBRQ_ST + (group * 16)); + he_writel(he_dev, 0x0, G0_RBRQ_H + (group * 16)); + he_writel(he_dev, RBRQ_THRESH(0x1) | RBRQ_SIZE(0x0), + G0_RBRQ_Q + (group * 16)); + he_writel(he_dev, 0x0, G0_RBRQ_I + (group * 16)); + + he_writel(he_dev, 0x0, G0_TBRQ_B_T + (group * 16)); + he_writel(he_dev, 0x0, G0_TBRQ_H + (group * 16)); + he_writel(he_dev, TBRQ_THRESH(0x1), + G0_TBRQ_THRESH + (group * 16)); + he_writel(he_dev, 0x0, G0_TBRQ_S + (group * 16)); + } + + /* host status page */ + + he_dev->hsp = dma_alloc_coherent(&he_dev->pci_dev->dev, + sizeof(struct he_hsp), + &he_dev->hsp_phys, GFP_KERNEL); + if (he_dev->hsp == NULL) { + hprintk("failed to allocate host status page\n"); + return -ENOMEM; + } + he_writel(he_dev, he_dev->hsp_phys, HSP_BA); + + /* initialize framer */ + +#ifdef CONFIG_ATM_HE_USE_SUNI + if (he_isMM(he_dev)) + suni_init(he_dev->atm_dev); + if (he_dev->atm_dev->phy && he_dev->atm_dev->phy->start) + he_dev->atm_dev->phy->start(he_dev->atm_dev); +#endif /* CONFIG_ATM_HE_USE_SUNI */ + + if (sdh) { + /* this really should be in suni.c but for now... */ + int val; + + val = he_phy_get(he_dev->atm_dev, SUNI_TPOP_APM); + val = (val & ~SUNI_TPOP_APM_S) | (SUNI_TPOP_S_SDH << SUNI_TPOP_APM_S_SHIFT); + he_phy_put(he_dev->atm_dev, val, SUNI_TPOP_APM); + he_phy_put(he_dev->atm_dev, SUNI_TACP_IUCHP_CLP, SUNI_TACP_IUCHP); + } + + /* 5.1.12 enable transmit and receive */ + + reg = he_readl_mbox(he_dev, CS_ERCTL0); + reg |= TX_ENABLE|ER_ENABLE; + he_writel_mbox(he_dev, reg, CS_ERCTL0); + + reg = he_readl(he_dev, RC_CONFIG); + reg |= RX_ENABLE; + he_writel(he_dev, reg, RC_CONFIG); + + for (i = 0; i < HE_NUM_CS_STPER; ++i) { + he_dev->cs_stper[i].inuse = 0; + he_dev->cs_stper[i].pcr = -1; + } + he_dev->total_bw = 0; + + + /* atm linux initialization */ + + he_dev->atm_dev->ci_range.vpi_bits = he_dev->vpibits; + he_dev->atm_dev->ci_range.vci_bits = he_dev->vcibits; + + he_dev->irq_peak = 0; + he_dev->rbrq_peak = 0; + he_dev->rbpl_peak = 0; + he_dev->tbrq_peak = 0; + + HPRINTK("hell bent for leather!\n"); + + return 0; +} + +static void +he_stop(struct he_dev *he_dev) +{ + struct he_buff *heb, *next; + struct pci_dev *pci_dev; + u32 gen_cntl_0, reg; + u16 command; + + pci_dev = he_dev->pci_dev; + + /* disable interrupts */ + + if (he_dev->membase) { + pci_read_config_dword(pci_dev, GEN_CNTL_0, &gen_cntl_0); + gen_cntl_0 &= ~(INT_PROC_ENBL | INIT_ENB); + pci_write_config_dword(pci_dev, GEN_CNTL_0, gen_cntl_0); + + tasklet_disable(&he_dev->tasklet); + + /* disable recv and transmit */ + + reg = he_readl_mbox(he_dev, CS_ERCTL0); + reg &= ~(TX_ENABLE|ER_ENABLE); + he_writel_mbox(he_dev, reg, CS_ERCTL0); + + reg = he_readl(he_dev, RC_CONFIG); + reg &= ~(RX_ENABLE); + he_writel(he_dev, reg, RC_CONFIG); + } + +#ifdef CONFIG_ATM_HE_USE_SUNI + if (he_dev->atm_dev->phy && he_dev->atm_dev->phy->stop) + he_dev->atm_dev->phy->stop(he_dev->atm_dev); +#endif /* CONFIG_ATM_HE_USE_SUNI */ + + if (he_dev->irq) + free_irq(he_dev->irq, he_dev); + + if (he_dev->irq_base) + dma_free_coherent(&he_dev->pci_dev->dev, (CONFIG_IRQ_SIZE + 1) + * sizeof(struct he_irq), he_dev->irq_base, he_dev->irq_phys); + + if (he_dev->hsp) + dma_free_coherent(&he_dev->pci_dev->dev, sizeof(struct he_hsp), + he_dev->hsp, he_dev->hsp_phys); + + if (he_dev->rbpl_base) { + list_for_each_entry_safe(heb, next, &he_dev->rbpl_outstanding, entry) + dma_pool_free(he_dev->rbpl_pool, heb, heb->mapping); + + dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_RBPL_SIZE + * sizeof(struct he_rbp), he_dev->rbpl_base, he_dev->rbpl_phys); + } + + kfree(he_dev->rbpl_virt); + kfree(he_dev->rbpl_table); + dma_pool_destroy(he_dev->rbpl_pool); + + if (he_dev->rbrq_base) + dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_RBRQ_SIZE * sizeof(struct he_rbrq), + he_dev->rbrq_base, he_dev->rbrq_phys); + + if (he_dev->tbrq_base) + dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq), + he_dev->tbrq_base, he_dev->tbrq_phys); + + if (he_dev->tpdrq_base) + dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq), + he_dev->tpdrq_base, he_dev->tpdrq_phys); + + dma_pool_destroy(he_dev->tpd_pool); + + if (he_dev->pci_dev) { + pci_read_config_word(he_dev->pci_dev, PCI_COMMAND, &command); + command &= ~(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); + pci_write_config_word(he_dev->pci_dev, PCI_COMMAND, command); + } + + if (he_dev->membase) + iounmap(he_dev->membase); +} + +static struct he_tpd * +__alloc_tpd(struct he_dev *he_dev) +{ + struct he_tpd *tpd; + dma_addr_t mapping; + + tpd = dma_pool_alloc(he_dev->tpd_pool, GFP_ATOMIC, &mapping); + if (tpd == NULL) + return NULL; + + tpd->status = TPD_ADDR(mapping); + tpd->reserved = 0; + tpd->iovec[0].addr = 0; tpd->iovec[0].len = 0; + tpd->iovec[1].addr = 0; tpd->iovec[1].len = 0; + tpd->iovec[2].addr = 0; tpd->iovec[2].len = 0; + + return tpd; +} + +#define AAL5_LEN(buf,len) \ + ((((unsigned char *)(buf))[(len)-6] << 8) | \ + (((unsigned char *)(buf))[(len)-5])) + +/* 2.10.1.2 receive + * + * aal5 packets can optionally return the tcp checksum in the lower + * 16 bits of the crc (RSR0_TCP_CKSUM) + */ + +#define TCP_CKSUM(buf,len) \ + ((((unsigned char *)(buf))[(len)-2] << 8) | \ + (((unsigned char *)(buf))[(len-1)])) + +static int +he_service_rbrq(struct he_dev *he_dev, int group) +{ + struct he_rbrq *rbrq_tail = (struct he_rbrq *) + ((unsigned long)he_dev->rbrq_base | + he_dev->hsp->group[group].rbrq_tail); + unsigned cid, lastcid = -1; + struct sk_buff *skb; + struct atm_vcc *vcc = NULL; + struct he_vcc *he_vcc; + struct he_buff *heb, *next; + int i; + int pdus_assembled = 0; + int updated = 0; + + read_lock(&vcc_sklist_lock); + while (he_dev->rbrq_head != rbrq_tail) { + ++updated; + + HPRINTK("%p rbrq%d 0x%x len=%d cid=0x%x %s%s%s%s%s%s\n", + he_dev->rbrq_head, group, + RBRQ_ADDR(he_dev->rbrq_head), + RBRQ_BUFLEN(he_dev->rbrq_head), + RBRQ_CID(he_dev->rbrq_head), + RBRQ_CRC_ERR(he_dev->rbrq_head) ? " CRC_ERR" : "", + RBRQ_LEN_ERR(he_dev->rbrq_head) ? " LEN_ERR" : "", + RBRQ_END_PDU(he_dev->rbrq_head) ? " END_PDU" : "", + RBRQ_AAL5_PROT(he_dev->rbrq_head) ? " AAL5_PROT" : "", + RBRQ_CON_CLOSED(he_dev->rbrq_head) ? " CON_CLOSED" : "", + RBRQ_HBUF_ERR(he_dev->rbrq_head) ? " HBUF_ERR" : ""); + + i = RBRQ_ADDR(he_dev->rbrq_head) >> RBP_IDX_OFFSET; + heb = he_dev->rbpl_virt[i]; + + cid = RBRQ_CID(he_dev->rbrq_head); + if (cid != lastcid) + vcc = __find_vcc(he_dev, cid); + lastcid = cid; + + if (vcc == NULL || (he_vcc = HE_VCC(vcc)) == NULL) { + hprintk("vcc/he_vcc == NULL (cid 0x%x)\n", cid); + if (!RBRQ_HBUF_ERR(he_dev->rbrq_head)) { + clear_bit(i, he_dev->rbpl_table); + list_del(&heb->entry); + dma_pool_free(he_dev->rbpl_pool, heb, heb->mapping); + } + + goto next_rbrq_entry; + } + + if (RBRQ_HBUF_ERR(he_dev->rbrq_head)) { + hprintk("HBUF_ERR! (cid 0x%x)\n", cid); + atomic_inc(&vcc->stats->rx_drop); + goto return_host_buffers; + } + + heb->len = RBRQ_BUFLEN(he_dev->rbrq_head) * 4; + clear_bit(i, he_dev->rbpl_table); + list_move_tail(&heb->entry, &he_vcc->buffers); + he_vcc->pdu_len += heb->len; + + if (RBRQ_CON_CLOSED(he_dev->rbrq_head)) { + lastcid = -1; + HPRINTK("wake_up rx_waitq (cid 0x%x)\n", cid); + wake_up(&he_vcc->rx_waitq); + goto return_host_buffers; + } + + if (!RBRQ_END_PDU(he_dev->rbrq_head)) + goto next_rbrq_entry; + + if (RBRQ_LEN_ERR(he_dev->rbrq_head) + || RBRQ_CRC_ERR(he_dev->rbrq_head)) { + HPRINTK("%s%s (%d.%d)\n", + RBRQ_CRC_ERR(he_dev->rbrq_head) + ? "CRC_ERR " : "", + RBRQ_LEN_ERR(he_dev->rbrq_head) + ? "LEN_ERR" : "", + vcc->vpi, vcc->vci); + atomic_inc(&vcc->stats->rx_err); + goto return_host_buffers; + } + + skb = atm_alloc_charge(vcc, he_vcc->pdu_len + rx_skb_reserve, + GFP_ATOMIC); + if (!skb) { + HPRINTK("charge failed (%d.%d)\n", vcc->vpi, vcc->vci); + goto return_host_buffers; + } + + if (rx_skb_reserve > 0) + skb_reserve(skb, rx_skb_reserve); + + __net_timestamp(skb); + + list_for_each_entry(heb, &he_vcc->buffers, entry) + skb_put_data(skb, &heb->data, heb->len); + + switch (vcc->qos.aal) { + case ATM_AAL0: + /* 2.10.1.5 raw cell receive */ + skb->len = ATM_AAL0_SDU; + skb_set_tail_pointer(skb, skb->len); + break; + case ATM_AAL5: + /* 2.10.1.2 aal5 receive */ + + skb->len = AAL5_LEN(skb->data, he_vcc->pdu_len); + skb_set_tail_pointer(skb, skb->len); +#ifdef USE_CHECKSUM_HW + if (vcc->vpi == 0 && vcc->vci >= ATM_NOT_RSV_VCI) { + skb->ip_summed = CHECKSUM_COMPLETE; + skb->csum = TCP_CKSUM(skb->data, + he_vcc->pdu_len); + } +#endif + break; + } + +#ifdef should_never_happen + if (skb->len > vcc->qos.rxtp.max_sdu) + hprintk("pdu_len (%d) > vcc->qos.rxtp.max_sdu (%d)! cid 0x%x\n", skb->len, vcc->qos.rxtp.max_sdu, cid); +#endif + +#ifdef notdef + ATM_SKB(skb)->vcc = vcc; +#endif + spin_unlock(&he_dev->global_lock); + vcc->push(vcc, skb); + spin_lock(&he_dev->global_lock); + + atomic_inc(&vcc->stats->rx); + +return_host_buffers: + ++pdus_assembled; + + list_for_each_entry_safe(heb, next, &he_vcc->buffers, entry) + dma_pool_free(he_dev->rbpl_pool, heb, heb->mapping); + INIT_LIST_HEAD(&he_vcc->buffers); + he_vcc->pdu_len = 0; + +next_rbrq_entry: + he_dev->rbrq_head = (struct he_rbrq *) + ((unsigned long) he_dev->rbrq_base | + RBRQ_MASK(he_dev->rbrq_head + 1)); + + } + read_unlock(&vcc_sklist_lock); + + if (updated) { + if (updated > he_dev->rbrq_peak) + he_dev->rbrq_peak = updated; + + he_writel(he_dev, RBRQ_MASK(he_dev->rbrq_head), + G0_RBRQ_H + (group * 16)); + } + + return pdus_assembled; +} + +static void +he_service_tbrq(struct he_dev *he_dev, int group) +{ + struct he_tbrq *tbrq_tail = (struct he_tbrq *) + ((unsigned long)he_dev->tbrq_base | + he_dev->hsp->group[group].tbrq_tail); + struct he_tpd *tpd; + int slot, updated = 0; + struct he_tpd *__tpd; + + /* 2.1.6 transmit buffer return queue */ + + while (he_dev->tbrq_head != tbrq_tail) { + ++updated; + + HPRINTK("tbrq%d 0x%x%s%s\n", + group, + TBRQ_TPD(he_dev->tbrq_head), + TBRQ_EOS(he_dev->tbrq_head) ? " EOS" : "", + TBRQ_MULTIPLE(he_dev->tbrq_head) ? " MULTIPLE" : ""); + tpd = NULL; + list_for_each_entry(__tpd, &he_dev->outstanding_tpds, entry) { + if (TPD_ADDR(__tpd->status) == TBRQ_TPD(he_dev->tbrq_head)) { + tpd = __tpd; + list_del(&__tpd->entry); + break; + } + } + + if (tpd == NULL) { + hprintk("unable to locate tpd for dma buffer %x\n", + TBRQ_TPD(he_dev->tbrq_head)); + goto next_tbrq_entry; + } + + if (TBRQ_EOS(he_dev->tbrq_head)) { + HPRINTK("wake_up(tx_waitq) cid 0x%x\n", + he_mkcid(he_dev, tpd->vcc->vpi, tpd->vcc->vci)); + if (tpd->vcc) + wake_up(&HE_VCC(tpd->vcc)->tx_waitq); + + goto next_tbrq_entry; + } + + for (slot = 0; slot < TPD_MAXIOV; ++slot) { + if (tpd->iovec[slot].addr) + dma_unmap_single(&he_dev->pci_dev->dev, + tpd->iovec[slot].addr, + tpd->iovec[slot].len & TPD_LEN_MASK, + DMA_TO_DEVICE); + if (tpd->iovec[slot].len & TPD_LST) + break; + + } + + if (tpd->skb) { /* && !TBRQ_MULTIPLE(he_dev->tbrq_head) */ + if (tpd->vcc && tpd->vcc->pop) + tpd->vcc->pop(tpd->vcc, tpd->skb); + else + dev_kfree_skb_any(tpd->skb); + } + +next_tbrq_entry: + if (tpd) + dma_pool_free(he_dev->tpd_pool, tpd, TPD_ADDR(tpd->status)); + he_dev->tbrq_head = (struct he_tbrq *) + ((unsigned long) he_dev->tbrq_base | + TBRQ_MASK(he_dev->tbrq_head + 1)); + } + + if (updated) { + if (updated > he_dev->tbrq_peak) + he_dev->tbrq_peak = updated; + + he_writel(he_dev, TBRQ_MASK(he_dev->tbrq_head), + G0_TBRQ_H + (group * 16)); + } +} + +static void +he_service_rbpl(struct he_dev *he_dev, int group) +{ + struct he_rbp *new_tail; + struct he_rbp *rbpl_head; + struct he_buff *heb; + dma_addr_t mapping; + int i; + int moved = 0; + + rbpl_head = (struct he_rbp *) ((unsigned long)he_dev->rbpl_base | + RBPL_MASK(he_readl(he_dev, G0_RBPL_S))); + + for (;;) { + new_tail = (struct he_rbp *) ((unsigned long)he_dev->rbpl_base | + RBPL_MASK(he_dev->rbpl_tail+1)); + + /* table 3.42 -- rbpl_tail should never be set to rbpl_head */ + if (new_tail == rbpl_head) + break; + + i = find_next_zero_bit(he_dev->rbpl_table, RBPL_TABLE_SIZE, he_dev->rbpl_hint); + if (i > (RBPL_TABLE_SIZE - 1)) { + i = find_first_zero_bit(he_dev->rbpl_table, RBPL_TABLE_SIZE); + if (i > (RBPL_TABLE_SIZE - 1)) + break; + } + he_dev->rbpl_hint = i + 1; + + heb = dma_pool_alloc(he_dev->rbpl_pool, GFP_ATOMIC, &mapping); + if (!heb) + break; + heb->mapping = mapping; + list_add(&heb->entry, &he_dev->rbpl_outstanding); + he_dev->rbpl_virt[i] = heb; + set_bit(i, he_dev->rbpl_table); + new_tail->idx = i << RBP_IDX_OFFSET; + new_tail->phys = mapping + offsetof(struct he_buff, data); + + he_dev->rbpl_tail = new_tail; + ++moved; + } + + if (moved) + he_writel(he_dev, RBPL_MASK(he_dev->rbpl_tail), G0_RBPL_T); +} + +static void +he_tasklet(unsigned long data) +{ + unsigned long flags; + struct he_dev *he_dev = (struct he_dev *) data; + int group, type; + int updated = 0; + + HPRINTK("tasklet (0x%lx)\n", data); + spin_lock_irqsave(&he_dev->global_lock, flags); + + while (he_dev->irq_head != he_dev->irq_tail) { + ++updated; + + type = ITYPE_TYPE(he_dev->irq_head->isw); + group = ITYPE_GROUP(he_dev->irq_head->isw); + + switch (type) { + case ITYPE_RBRQ_THRESH: + HPRINTK("rbrq%d threshold\n", group); + fallthrough; + case ITYPE_RBRQ_TIMER: + if (he_service_rbrq(he_dev, group)) + he_service_rbpl(he_dev, group); + break; + case ITYPE_TBRQ_THRESH: + HPRINTK("tbrq%d threshold\n", group); + fallthrough; + case ITYPE_TPD_COMPLETE: + he_service_tbrq(he_dev, group); + break; + case ITYPE_RBPL_THRESH: + he_service_rbpl(he_dev, group); + break; + case ITYPE_RBPS_THRESH: + /* shouldn't happen unless small buffers enabled */ + break; + case ITYPE_PHY: + HPRINTK("phy interrupt\n"); +#ifdef CONFIG_ATM_HE_USE_SUNI + spin_unlock_irqrestore(&he_dev->global_lock, flags); + if (he_dev->atm_dev->phy && he_dev->atm_dev->phy->interrupt) + he_dev->atm_dev->phy->interrupt(he_dev->atm_dev); + spin_lock_irqsave(&he_dev->global_lock, flags); +#endif + break; + case ITYPE_OTHER: + switch (type|group) { + case ITYPE_PARITY: + hprintk("parity error\n"); + break; + case ITYPE_ABORT: + hprintk("abort 0x%x\n", he_readl(he_dev, ABORT_ADDR)); + break; + } + break; + case ITYPE_TYPE(ITYPE_INVALID): + /* see 8.1.1 -- check all queues */ + + HPRINTK("isw not updated 0x%x\n", he_dev->irq_head->isw); + + he_service_rbrq(he_dev, 0); + he_service_rbpl(he_dev, 0); + he_service_tbrq(he_dev, 0); + break; + default: + hprintk("bad isw 0x%x?\n", he_dev->irq_head->isw); + } + + he_dev->irq_head->isw = ITYPE_INVALID; + + he_dev->irq_head = (struct he_irq *) NEXT_ENTRY(he_dev->irq_base, he_dev->irq_head, IRQ_MASK); + } + + if (updated) { + if (updated > he_dev->irq_peak) + he_dev->irq_peak = updated; + + he_writel(he_dev, + IRQ_SIZE(CONFIG_IRQ_SIZE) | + IRQ_THRESH(CONFIG_IRQ_THRESH) | + IRQ_TAIL(he_dev->irq_tail), IRQ0_HEAD); + (void) he_readl(he_dev, INT_FIFO); /* 8.1.2 controller errata; flush posted writes */ + } + spin_unlock_irqrestore(&he_dev->global_lock, flags); +} + +static irqreturn_t +he_irq_handler(int irq, void *dev_id) +{ + unsigned long flags; + struct he_dev *he_dev = (struct he_dev * )dev_id; + int handled = 0; + + if (he_dev == NULL) + return IRQ_NONE; + + spin_lock_irqsave(&he_dev->global_lock, flags); + + he_dev->irq_tail = (struct he_irq *) (((unsigned long)he_dev->irq_base) | + (*he_dev->irq_tailoffset << 2)); + + if (he_dev->irq_tail == he_dev->irq_head) { + HPRINTK("tailoffset not updated?\n"); + he_dev->irq_tail = (struct he_irq *) ((unsigned long)he_dev->irq_base | + ((he_readl(he_dev, IRQ0_BASE) & IRQ_MASK) << 2)); + (void) he_readl(he_dev, INT_FIFO); /* 8.1.2 controller errata */ + } + +#ifdef DEBUG + if (he_dev->irq_head == he_dev->irq_tail /* && !IRQ_PENDING */) + hprintk("spurious (or shared) interrupt?\n"); +#endif + + if (he_dev->irq_head != he_dev->irq_tail) { + handled = 1; + tasklet_schedule(&he_dev->tasklet); + he_writel(he_dev, INT_CLEAR_A, INT_FIFO); /* clear interrupt */ + (void) he_readl(he_dev, INT_FIFO); /* flush posted writes */ + } + spin_unlock_irqrestore(&he_dev->global_lock, flags); + return IRQ_RETVAL(handled); + +} + +static __inline__ void +__enqueue_tpd(struct he_dev *he_dev, struct he_tpd *tpd, unsigned cid) +{ + struct he_tpdrq *new_tail; + + HPRINTK("tpdrq %p cid 0x%x -> tpdrq_tail %p\n", + tpd, cid, he_dev->tpdrq_tail); + + /* new_tail = he_dev->tpdrq_tail; */ + new_tail = (struct he_tpdrq *) ((unsigned long) he_dev->tpdrq_base | + TPDRQ_MASK(he_dev->tpdrq_tail+1)); + + /* + * check to see if we are about to set the tail == head + * if true, update the head pointer from the adapter + * to see if this is really the case (reading the queue + * head for every enqueue would be unnecessarily slow) + */ + + if (new_tail == he_dev->tpdrq_head) { + he_dev->tpdrq_head = (struct he_tpdrq *) + (((unsigned long)he_dev->tpdrq_base) | + TPDRQ_MASK(he_readl(he_dev, TPDRQ_B_H))); + + if (new_tail == he_dev->tpdrq_head) { + int slot; + + hprintk("tpdrq full (cid 0x%x)\n", cid); + /* + * FIXME + * push tpd onto a transmit backlog queue + * after service_tbrq, service the backlog + * for now, we just drop the pdu + */ + for (slot = 0; slot < TPD_MAXIOV; ++slot) { + if (tpd->iovec[slot].addr) + dma_unmap_single(&he_dev->pci_dev->dev, + tpd->iovec[slot].addr, + tpd->iovec[slot].len & TPD_LEN_MASK, + DMA_TO_DEVICE); + } + if (tpd->skb) { + if (tpd->vcc->pop) + tpd->vcc->pop(tpd->vcc, tpd->skb); + else + dev_kfree_skb_any(tpd->skb); + atomic_inc(&tpd->vcc->stats->tx_err); + } + dma_pool_free(he_dev->tpd_pool, tpd, TPD_ADDR(tpd->status)); + return; + } + } + + /* 2.1.5 transmit packet descriptor ready queue */ + list_add_tail(&tpd->entry, &he_dev->outstanding_tpds); + he_dev->tpdrq_tail->tpd = TPD_ADDR(tpd->status); + he_dev->tpdrq_tail->cid = cid; + wmb(); + + he_dev->tpdrq_tail = new_tail; + + he_writel(he_dev, TPDRQ_MASK(he_dev->tpdrq_tail), TPDRQ_T); + (void) he_readl(he_dev, TPDRQ_T); /* flush posted writes */ +} + +static int +he_open(struct atm_vcc *vcc) +{ + unsigned long flags; + struct he_dev *he_dev = HE_DEV(vcc->dev); + struct he_vcc *he_vcc; + int err = 0; + unsigned cid, rsr0, rsr1, rsr4, tsr0, tsr0_aal, tsr4, period, reg, clock; + short vpi = vcc->vpi; + int vci = vcc->vci; + + if (vci == ATM_VCI_UNSPEC || vpi == ATM_VPI_UNSPEC) + return 0; + + HPRINTK("open vcc %p %d.%d\n", vcc, vpi, vci); + + set_bit(ATM_VF_ADDR, &vcc->flags); + + cid = he_mkcid(he_dev, vpi, vci); + + he_vcc = kmalloc(sizeof(struct he_vcc), GFP_ATOMIC); + if (he_vcc == NULL) { + hprintk("unable to allocate he_vcc during open\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&he_vcc->buffers); + he_vcc->pdu_len = 0; + he_vcc->rc_index = -1; + + init_waitqueue_head(&he_vcc->rx_waitq); + init_waitqueue_head(&he_vcc->tx_waitq); + + vcc->dev_data = he_vcc; + + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + int pcr_goal; + + pcr_goal = atm_pcr_goal(&vcc->qos.txtp); + if (pcr_goal == 0) + pcr_goal = he_dev->atm_dev->link_rate; + if (pcr_goal < 0) /* means round down, technically */ + pcr_goal = -pcr_goal; + + HPRINTK("open tx cid 0x%x pcr_goal %d\n", cid, pcr_goal); + + switch (vcc->qos.aal) { + case ATM_AAL5: + tsr0_aal = TSR0_AAL5; + tsr4 = TSR4_AAL5; + break; + case ATM_AAL0: + tsr0_aal = TSR0_AAL0_SDU; + tsr4 = TSR4_AAL0_SDU; + break; + default: + err = -EINVAL; + goto open_failed; + } + + spin_lock_irqsave(&he_dev->global_lock, flags); + tsr0 = he_readl_tsr0(he_dev, cid); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + if (TSR0_CONN_STATE(tsr0) != 0) { + hprintk("cid 0x%x not idle (tsr0 = 0x%x)\n", cid, tsr0); + err = -EBUSY; + goto open_failed; + } + + switch (vcc->qos.txtp.traffic_class) { + case ATM_UBR: + /* 2.3.3.1 open connection ubr */ + + tsr0 = TSR0_UBR | TSR0_GROUP(0) | tsr0_aal | + TSR0_USE_WMIN | TSR0_UPDATE_GER; + break; + + case ATM_CBR: + /* 2.3.3.2 open connection cbr */ + + /* 8.2.3 cbr scheduler wrap problem -- limit to 90% total link rate */ + if ((he_dev->total_bw + pcr_goal) + > (he_dev->atm_dev->link_rate * 9 / 10)) + { + err = -EBUSY; + goto open_failed; + } + + spin_lock_irqsave(&he_dev->global_lock, flags); /* also protects he_dev->cs_stper[] */ + + /* find an unused cs_stper register */ + for (reg = 0; reg < HE_NUM_CS_STPER; ++reg) + if (he_dev->cs_stper[reg].inuse == 0 || + he_dev->cs_stper[reg].pcr == pcr_goal) + break; + + if (reg == HE_NUM_CS_STPER) { + err = -EBUSY; + spin_unlock_irqrestore(&he_dev->global_lock, flags); + goto open_failed; + } + + he_dev->total_bw += pcr_goal; + + he_vcc->rc_index = reg; + ++he_dev->cs_stper[reg].inuse; + he_dev->cs_stper[reg].pcr = pcr_goal; + + clock = he_is622(he_dev) ? 66667000 : 50000000; + period = clock / pcr_goal; + + HPRINTK("rc_index = %d period = %d\n", + reg, period); + + he_writel_mbox(he_dev, rate_to_atmf(period/2), + CS_STPER0 + reg); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + tsr0 = TSR0_CBR | TSR0_GROUP(0) | tsr0_aal | + TSR0_RC_INDEX(reg); + + break; + default: + err = -EINVAL; + goto open_failed; + } + + spin_lock_irqsave(&he_dev->global_lock, flags); + + he_writel_tsr0(he_dev, tsr0, cid); + he_writel_tsr4(he_dev, tsr4 | 1, cid); + he_writel_tsr1(he_dev, TSR1_MCR(rate_to_atmf(0)) | + TSR1_PCR(rate_to_atmf(pcr_goal)), cid); + he_writel_tsr2(he_dev, TSR2_ACR(rate_to_atmf(pcr_goal)), cid); + he_writel_tsr9(he_dev, TSR9_OPEN_CONN, cid); + + he_writel_tsr3(he_dev, 0x0, cid); + he_writel_tsr5(he_dev, 0x0, cid); + he_writel_tsr6(he_dev, 0x0, cid); + he_writel_tsr7(he_dev, 0x0, cid); + he_writel_tsr8(he_dev, 0x0, cid); + he_writel_tsr10(he_dev, 0x0, cid); + he_writel_tsr11(he_dev, 0x0, cid); + he_writel_tsr12(he_dev, 0x0, cid); + he_writel_tsr13(he_dev, 0x0, cid); + he_writel_tsr14(he_dev, 0x0, cid); + (void) he_readl_tsr0(he_dev, cid); /* flush posted writes */ + spin_unlock_irqrestore(&he_dev->global_lock, flags); + } + + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + unsigned aal; + + HPRINTK("open rx cid 0x%x (rx_waitq %p)\n", cid, + &HE_VCC(vcc)->rx_waitq); + + switch (vcc->qos.aal) { + case ATM_AAL5: + aal = RSR0_AAL5; + break; + case ATM_AAL0: + aal = RSR0_RAWCELL; + break; + default: + err = -EINVAL; + goto open_failed; + } + + spin_lock_irqsave(&he_dev->global_lock, flags); + + rsr0 = he_readl_rsr0(he_dev, cid); + if (rsr0 & RSR0_OPEN_CONN) { + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + hprintk("cid 0x%x not idle (rsr0 = 0x%x)\n", cid, rsr0); + err = -EBUSY; + goto open_failed; + } + + rsr1 = RSR1_GROUP(0) | RSR1_RBPL_ONLY; + rsr4 = RSR4_GROUP(0) | RSR4_RBPL_ONLY; + rsr0 = vcc->qos.rxtp.traffic_class == ATM_UBR ? + (RSR0_EPD_ENABLE|RSR0_PPD_ENABLE) : 0; + +#ifdef USE_CHECKSUM_HW + if (vpi == 0 && vci >= ATM_NOT_RSV_VCI) + rsr0 |= RSR0_TCP_CKSUM; +#endif + + he_writel_rsr4(he_dev, rsr4, cid); + he_writel_rsr1(he_dev, rsr1, cid); + /* 5.1.11 last parameter initialized should be + the open/closed indication in rsr0 */ + he_writel_rsr0(he_dev, + rsr0 | RSR0_START_PDU | RSR0_OPEN_CONN | aal, cid); + (void) he_readl_rsr0(he_dev, cid); /* flush posted writes */ + + spin_unlock_irqrestore(&he_dev->global_lock, flags); + } + +open_failed: + + if (err) { + kfree(he_vcc); + clear_bit(ATM_VF_ADDR, &vcc->flags); + } + else + set_bit(ATM_VF_READY, &vcc->flags); + + return err; +} + +static void +he_close(struct atm_vcc *vcc) +{ + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + struct he_dev *he_dev = HE_DEV(vcc->dev); + struct he_tpd *tpd; + unsigned cid; + struct he_vcc *he_vcc = HE_VCC(vcc); +#define MAX_RETRY 30 + int retry = 0, sleep = 1, tx_inuse; + + HPRINTK("close vcc %p %d.%d\n", vcc, vcc->vpi, vcc->vci); + + clear_bit(ATM_VF_READY, &vcc->flags); + cid = he_mkcid(he_dev, vcc->vpi, vcc->vci); + + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + int timeout; + + HPRINTK("close rx cid 0x%x\n", cid); + + /* 2.7.2.2 close receive operation */ + + /* wait for previous close (if any) to finish */ + + spin_lock_irqsave(&he_dev->global_lock, flags); + while (he_readl(he_dev, RCC_STAT) & RCC_BUSY) { + HPRINTK("close cid 0x%x RCC_BUSY\n", cid); + udelay(250); + } + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&he_vcc->rx_waitq, &wait); + + he_writel_rsr0(he_dev, RSR0_CLOSE_CONN, cid); + (void) he_readl_rsr0(he_dev, cid); /* flush posted writes */ + he_writel_mbox(he_dev, cid, RXCON_CLOSE); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + timeout = schedule_timeout(30*HZ); + + remove_wait_queue(&he_vcc->rx_waitq, &wait); + set_current_state(TASK_RUNNING); + + if (timeout == 0) + hprintk("close rx timeout cid 0x%x\n", cid); + + HPRINTK("close rx cid 0x%x complete\n", cid); + + } + + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + volatile unsigned tsr4, tsr0; + int timeout; + + HPRINTK("close tx cid 0x%x\n", cid); + + /* 2.1.2 + * + * ... the host must first stop queueing packets to the TPDRQ + * on the connection to be closed, then wait for all outstanding + * packets to be transmitted and their buffers returned to the + * TBRQ. When the last packet on the connection arrives in the + * TBRQ, the host issues the close command to the adapter. + */ + + while (((tx_inuse = refcount_read(&sk_atm(vcc)->sk_wmem_alloc)) > 1) && + (retry < MAX_RETRY)) { + msleep(sleep); + if (sleep < 250) + sleep = sleep * 2; + + ++retry; + } + + if (tx_inuse > 1) + hprintk("close tx cid 0x%x tx_inuse = %d\n", cid, tx_inuse); + + /* 2.3.1.1 generic close operations with flush */ + + spin_lock_irqsave(&he_dev->global_lock, flags); + he_writel_tsr4_upper(he_dev, TSR4_FLUSH_CONN, cid); + /* also clears TSR4_SESSION_ENDED */ + + switch (vcc->qos.txtp.traffic_class) { + case ATM_UBR: + he_writel_tsr1(he_dev, + TSR1_MCR(rate_to_atmf(200000)) + | TSR1_PCR(0), cid); + break; + case ATM_CBR: + he_writel_tsr14_upper(he_dev, TSR14_DELETE, cid); + break; + } + (void) he_readl_tsr4(he_dev, cid); /* flush posted writes */ + + tpd = __alloc_tpd(he_dev); + if (tpd == NULL) { + hprintk("close tx he_alloc_tpd failed cid 0x%x\n", cid); + goto close_tx_incomplete; + } + tpd->status |= TPD_EOS | TPD_INT; + tpd->skb = NULL; + tpd->vcc = vcc; + wmb(); + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&he_vcc->tx_waitq, &wait); + __enqueue_tpd(he_dev, tpd, cid); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + timeout = schedule_timeout(30*HZ); + + remove_wait_queue(&he_vcc->tx_waitq, &wait); + set_current_state(TASK_RUNNING); + + spin_lock_irqsave(&he_dev->global_lock, flags); + + if (timeout == 0) { + hprintk("close tx timeout cid 0x%x\n", cid); + goto close_tx_incomplete; + } + + while (!((tsr4 = he_readl_tsr4(he_dev, cid)) & TSR4_SESSION_ENDED)) { + HPRINTK("close tx cid 0x%x !TSR4_SESSION_ENDED (tsr4 = 0x%x)\n", cid, tsr4); + udelay(250); + } + + while (TSR0_CONN_STATE(tsr0 = he_readl_tsr0(he_dev, cid)) != 0) { + HPRINTK("close tx cid 0x%x TSR0_CONN_STATE != 0 (tsr0 = 0x%x)\n", cid, tsr0); + udelay(250); + } + +close_tx_incomplete: + + if (vcc->qos.txtp.traffic_class == ATM_CBR) { + int reg = he_vcc->rc_index; + + HPRINTK("cs_stper reg = %d\n", reg); + + if (he_dev->cs_stper[reg].inuse == 0) + hprintk("cs_stper[%d].inuse = 0!\n", reg); + else + --he_dev->cs_stper[reg].inuse; + + he_dev->total_bw -= he_dev->cs_stper[reg].pcr; + } + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + HPRINTK("close tx cid 0x%x complete\n", cid); + } + + kfree(he_vcc); + + clear_bit(ATM_VF_ADDR, &vcc->flags); +} + +static int +he_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + unsigned long flags; + struct he_dev *he_dev = HE_DEV(vcc->dev); + unsigned cid = he_mkcid(he_dev, vcc->vpi, vcc->vci); + struct he_tpd *tpd; +#ifdef USE_SCATTERGATHER + int i, slot = 0; +#endif + +#define HE_TPD_BUFSIZE 0xffff + + HPRINTK("send %d.%d\n", vcc->vpi, vcc->vci); + + if ((skb->len > HE_TPD_BUFSIZE) || + ((vcc->qos.aal == ATM_AAL0) && (skb->len != ATM_AAL0_SDU))) { + hprintk("buffer too large (or small) -- %d bytes\n", skb->len ); + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + atomic_inc(&vcc->stats->tx_err); + return -EINVAL; + } + +#ifndef USE_SCATTERGATHER + if (skb_shinfo(skb)->nr_frags) { + hprintk("no scatter/gather support\n"); + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + atomic_inc(&vcc->stats->tx_err); + return -EINVAL; + } +#endif + spin_lock_irqsave(&he_dev->global_lock, flags); + + tpd = __alloc_tpd(he_dev); + if (tpd == NULL) { + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + atomic_inc(&vcc->stats->tx_err); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + return -ENOMEM; + } + + if (vcc->qos.aal == ATM_AAL5) + tpd->status |= TPD_CELLTYPE(TPD_USERCELL); + else { + char *pti_clp = (void *) (skb->data + 3); + int clp, pti; + + pti = (*pti_clp & ATM_HDR_PTI_MASK) >> ATM_HDR_PTI_SHIFT; + clp = (*pti_clp & ATM_HDR_CLP); + tpd->status |= TPD_CELLTYPE(pti); + if (clp) + tpd->status |= TPD_CLP; + + skb_pull(skb, ATM_AAL0_SDU - ATM_CELL_PAYLOAD); + } + +#ifdef USE_SCATTERGATHER + tpd->iovec[slot].addr = dma_map_single(&he_dev->pci_dev->dev, skb->data, + skb_headlen(skb), DMA_TO_DEVICE); + tpd->iovec[slot].len = skb_headlen(skb); + ++slot; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + + if (slot == TPD_MAXIOV) { /* queue tpd; start new tpd */ + tpd->vcc = vcc; + tpd->skb = NULL; /* not the last fragment + so dont ->push() yet */ + wmb(); + + __enqueue_tpd(he_dev, tpd, cid); + tpd = __alloc_tpd(he_dev); + if (tpd == NULL) { + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + atomic_inc(&vcc->stats->tx_err); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + return -ENOMEM; + } + tpd->status |= TPD_USERCELL; + slot = 0; + } + + tpd->iovec[slot].addr = skb_frag_dma_map(&he_dev->pci_dev->dev, + frag, 0, skb_frag_size(frag), DMA_TO_DEVICE); + tpd->iovec[slot].len = skb_frag_size(frag); + ++slot; + + } + + tpd->iovec[slot - 1].len |= TPD_LST; +#else + tpd->address0 = dma_map_single(&he_dev->pci_dev->dev, skb->data, skb->len, DMA_TO_DEVICE); + tpd->length0 = skb->len | TPD_LST; +#endif + tpd->status |= TPD_INT; + + tpd->vcc = vcc; + tpd->skb = skb; + wmb(); + ATM_SKB(skb)->vcc = vcc; + + __enqueue_tpd(he_dev, tpd, cid); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + atomic_inc(&vcc->stats->tx); + + return 0; +} + +static int +he_ioctl(struct atm_dev *atm_dev, unsigned int cmd, void __user *arg) +{ + unsigned long flags; + struct he_dev *he_dev = HE_DEV(atm_dev); + struct he_ioctl_reg reg; + int err = 0; + + switch (cmd) { + case HE_GET_REG: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (copy_from_user(®, arg, + sizeof(struct he_ioctl_reg))) + return -EFAULT; + + spin_lock_irqsave(&he_dev->global_lock, flags); + switch (reg.type) { + case HE_REGTYPE_PCI: + if (reg.addr >= HE_REGMAP_SIZE) { + err = -EINVAL; + break; + } + + reg.val = he_readl(he_dev, reg.addr); + break; + case HE_REGTYPE_RCM: + reg.val = + he_readl_rcm(he_dev, reg.addr); + break; + case HE_REGTYPE_TCM: + reg.val = + he_readl_tcm(he_dev, reg.addr); + break; + case HE_REGTYPE_MBOX: + reg.val = + he_readl_mbox(he_dev, reg.addr); + break; + default: + err = -EINVAL; + break; + } + spin_unlock_irqrestore(&he_dev->global_lock, flags); + if (err == 0) + if (copy_to_user(arg, ®, + sizeof(struct he_ioctl_reg))) + return -EFAULT; + break; + default: +#ifdef CONFIG_ATM_HE_USE_SUNI + if (atm_dev->phy && atm_dev->phy->ioctl) + err = atm_dev->phy->ioctl(atm_dev, cmd, arg); +#else /* CONFIG_ATM_HE_USE_SUNI */ + err = -EINVAL; +#endif /* CONFIG_ATM_HE_USE_SUNI */ + break; + } + + return err; +} + +static void +he_phy_put(struct atm_dev *atm_dev, unsigned char val, unsigned long addr) +{ + unsigned long flags; + struct he_dev *he_dev = HE_DEV(atm_dev); + + HPRINTK("phy_put(val 0x%x, addr 0x%lx)\n", val, addr); + + spin_lock_irqsave(&he_dev->global_lock, flags); + he_writel(he_dev, val, FRAMER + (addr*4)); + (void) he_readl(he_dev, FRAMER + (addr*4)); /* flush posted writes */ + spin_unlock_irqrestore(&he_dev->global_lock, flags); +} + + +static unsigned char +he_phy_get(struct atm_dev *atm_dev, unsigned long addr) +{ + unsigned long flags; + struct he_dev *he_dev = HE_DEV(atm_dev); + unsigned reg; + + spin_lock_irqsave(&he_dev->global_lock, flags); + reg = he_readl(he_dev, FRAMER + (addr*4)); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + HPRINTK("phy_get(addr 0x%lx) =0x%x\n", addr, reg); + return reg; +} + +static int +he_proc_read(struct atm_dev *dev, loff_t *pos, char *page) +{ + unsigned long flags; + struct he_dev *he_dev = HE_DEV(dev); + int left, i; +#ifdef notdef + struct he_rbrq *rbrq_tail; + struct he_tpdrq *tpdrq_head; + int rbpl_head, rbpl_tail; +#endif + static long mcc = 0, oec = 0, dcc = 0, cec = 0; + + + left = *pos; + if (!left--) + return sprintf(page, "ATM he driver\n"); + + if (!left--) + return sprintf(page, "%s%s\n\n", + he_dev->prod_id, he_dev->media & 0x40 ? "SM" : "MM"); + + if (!left--) + return sprintf(page, "Mismatched Cells VPI/VCI Not Open Dropped Cells RCM Dropped Cells\n"); + + spin_lock_irqsave(&he_dev->global_lock, flags); + mcc += he_readl(he_dev, MCC); + oec += he_readl(he_dev, OEC); + dcc += he_readl(he_dev, DCC); + cec += he_readl(he_dev, CEC); + spin_unlock_irqrestore(&he_dev->global_lock, flags); + + if (!left--) + return sprintf(page, "%16ld %16ld %13ld %17ld\n\n", + mcc, oec, dcc, cec); + + if (!left--) + return sprintf(page, "irq_size = %d inuse = ? peak = %d\n", + CONFIG_IRQ_SIZE, he_dev->irq_peak); + + if (!left--) + return sprintf(page, "tpdrq_size = %d inuse = ?\n", + CONFIG_TPDRQ_SIZE); + + if (!left--) + return sprintf(page, "rbrq_size = %d inuse = ? peak = %d\n", + CONFIG_RBRQ_SIZE, he_dev->rbrq_peak); + + if (!left--) + return sprintf(page, "tbrq_size = %d peak = %d\n", + CONFIG_TBRQ_SIZE, he_dev->tbrq_peak); + + +#ifdef notdef + rbpl_head = RBPL_MASK(he_readl(he_dev, G0_RBPL_S)); + rbpl_tail = RBPL_MASK(he_readl(he_dev, G0_RBPL_T)); + + inuse = rbpl_head - rbpl_tail; + if (inuse < 0) + inuse += CONFIG_RBPL_SIZE * sizeof(struct he_rbp); + inuse /= sizeof(struct he_rbp); + + if (!left--) + return sprintf(page, "rbpl_size = %d inuse = %d\n\n", + CONFIG_RBPL_SIZE, inuse); +#endif + + if (!left--) + return sprintf(page, "rate controller periods (cbr)\n pcr #vc\n"); + + for (i = 0; i < HE_NUM_CS_STPER; ++i) + if (!left--) + return sprintf(page, "cs_stper%-2d %8ld %3d\n", i, + he_dev->cs_stper[i].pcr, + he_dev->cs_stper[i].inuse); + + if (!left--) + return sprintf(page, "total bw (cbr): %d (limit %d)\n", + he_dev->total_bw, he_dev->atm_dev->link_rate * 10 / 9); + + return 0; +} + +/* eeprom routines -- see 4.7 */ + +static u8 read_prom_byte(struct he_dev *he_dev, int addr) +{ + u32 val = 0, tmp_read = 0; + int i, j = 0; + u8 byte_read = 0; + + val = readl(he_dev->membase + HOST_CNTL); + val &= 0xFFFFE0FF; + + /* Turn on write enable */ + val |= 0x800; + he_writel(he_dev, val, HOST_CNTL); + + /* Send READ instruction */ + for (i = 0; i < ARRAY_SIZE(readtab); i++) { + he_writel(he_dev, val | readtab[i], HOST_CNTL); + udelay(EEPROM_DELAY); + } + + /* Next, we need to send the byte address to read from */ + for (i = 7; i >= 0; i--) { + he_writel(he_dev, val | clocktab[j++] | (((addr >> i) & 1) << 9), HOST_CNTL); + udelay(EEPROM_DELAY); + he_writel(he_dev, val | clocktab[j++] | (((addr >> i) & 1) << 9), HOST_CNTL); + udelay(EEPROM_DELAY); + } + + j = 0; + + val &= 0xFFFFF7FF; /* Turn off write enable */ + he_writel(he_dev, val, HOST_CNTL); + + /* Now, we can read data from the EEPROM by clocking it in */ + for (i = 7; i >= 0; i--) { + he_writel(he_dev, val | clocktab[j++], HOST_CNTL); + udelay(EEPROM_DELAY); + tmp_read = he_readl(he_dev, HOST_CNTL); + byte_read |= (unsigned char) + ((tmp_read & ID_DOUT) >> ID_DOFFSET << i); + he_writel(he_dev, val | clocktab[j++], HOST_CNTL); + udelay(EEPROM_DELAY); + } + + he_writel(he_dev, val | ID_CS, HOST_CNTL); + udelay(EEPROM_DELAY); + + return byte_read; +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("chas williams "); +MODULE_DESCRIPTION("ForeRunnerHE ATM Adapter driver"); +module_param(disable64, bool, 0); +MODULE_PARM_DESC(disable64, "disable 64-bit pci bus transfers"); +module_param(nvpibits, short, 0); +MODULE_PARM_DESC(nvpibits, "numbers of bits for vpi (default 0)"); +module_param(nvcibits, short, 0); +MODULE_PARM_DESC(nvcibits, "numbers of bits for vci (default 12)"); +module_param(rx_skb_reserve, short, 0); +MODULE_PARM_DESC(rx_skb_reserve, "padding for receive skb (default 16)"); +module_param(irq_coalesce, bool, 0); +MODULE_PARM_DESC(irq_coalesce, "use interrupt coalescing (default 1)"); +module_param(sdh, bool, 0); +MODULE_PARM_DESC(sdh, "use SDH framing (default 0)"); + +static const struct pci_device_id he_pci_tbl[] = { + { PCI_VDEVICE(FORE, PCI_DEVICE_ID_FORE_HE), 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, he_pci_tbl); + +static struct pci_driver he_driver = { + .name = "he", + .probe = he_init_one, + .remove = he_remove_one, + .id_table = he_pci_tbl, +}; + +module_pci_driver(he_driver); diff --git a/drivers/atm/he.h b/drivers/atm/he.h new file mode 100644 index 000000000..f3f53674e --- /dev/null +++ b/drivers/atm/he.h @@ -0,0 +1,845 @@ +/* + + he.h + + ForeRunnerHE ATM Adapter driver for ATM on Linux + Copyright (C) 1999-2001 Naval Research Laboratory + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/* + + he.h + + ForeRunnerHE ATM Adapter driver for ATM on Linux + Copyright (C) 1999-2000 Naval Research Laboratory + + Permission to use, copy, modify and distribute this software and its + documentation is hereby granted, provided that both the copyright + notice and this permission notice appear in all copies of the software, + derivative works or modified versions, and any portions thereof, and + that both notices appear in supporting documentation. + + NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND + DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER + RESULTING FROM THE USE OF THIS SOFTWARE. + + */ + +#ifndef _HE_H_ +#define _HE_H_ + +#define DEV_LABEL "he" + +#define CONFIG_DEFAULT_VCIBITS 12 +#define CONFIG_DEFAULT_VPIBITS 0 + +#define CONFIG_IRQ_SIZE 128 +#define CONFIG_IRQ_THRESH (CONFIG_IRQ_SIZE/2) + +#define CONFIG_TPDRQ_SIZE 512 +#define TPDRQ_MASK(x) (((unsigned long)(x))&((CONFIG_TPDRQ_SIZE<<3)-1)) + +#define CONFIG_RBRQ_SIZE 512 +#define CONFIG_RBRQ_THRESH 400 +#define RBRQ_MASK(x) (((unsigned long)(x))&((CONFIG_RBRQ_SIZE<<3)-1)) + +#define CONFIG_TBRQ_SIZE 512 +#define CONFIG_TBRQ_THRESH 400 +#define TBRQ_MASK(x) (((unsigned long)(x))&((CONFIG_TBRQ_SIZE<<2)-1)) + +#define CONFIG_RBPL_SIZE 512 +#define CONFIG_RBPL_THRESH 64 +#define CONFIG_RBPL_BUFSIZE 4096 +#define RBPL_MASK(x) (((unsigned long)(x))&((CONFIG_RBPL_SIZE<<3)-1)) + +/* 5.1.3 initialize connection memory */ + +#define CONFIG_RSRA 0x00000 +#define CONFIG_RCMLBM 0x08000 +#define CONFIG_RCMABR 0x0d800 +#define CONFIG_RSRB 0x0e000 + +#define CONFIG_TSRA 0x00000 +#define CONFIG_TSRB 0x08000 +#define CONFIG_TSRC 0x0c000 +#define CONFIG_TSRD 0x0e000 +#define CONFIG_TMABR 0x0f000 +#define CONFIG_TPDBA 0x10000 + +#define HE_MAXCIDBITS 12 + +/* 2.9.3.3 interrupt encodings */ + +struct he_irq { + volatile u32 isw; +}; + +#define IRQ_ALIGNMENT 0x1000 + +#define NEXT_ENTRY(base, tail, mask) \ + (((unsigned long)base)|(((unsigned long)(tail+1))&mask)) + +#define ITYPE_INVALID 0xffffffff +#define ITYPE_TBRQ_THRESH (0<<3) +#define ITYPE_TPD_COMPLETE (1<<3) +#define ITYPE_RBPS_THRESH (2<<3) +#define ITYPE_RBPL_THRESH (3<<3) +#define ITYPE_RBRQ_THRESH (4<<3) +#define ITYPE_RBRQ_TIMER (5<<3) +#define ITYPE_PHY (6<<3) +#define ITYPE_OTHER 0x80 +#define ITYPE_PARITY 0x81 +#define ITYPE_ABORT 0x82 + +#define ITYPE_GROUP(x) (x & 0x7) +#define ITYPE_TYPE(x) (x & 0xf8) + +#define HE_NUM_GROUPS 8 + +/* 2.1.4 transmit packet descriptor */ + +struct he_tpd { + + /* read by the adapter */ + + volatile u32 status; + volatile u32 reserved; + +#define TPD_MAXIOV 3 + struct { + u32 addr, len; + } iovec[TPD_MAXIOV]; + +#define address0 iovec[0].addr +#define length0 iovec[0].len + + /* linux-atm extensions */ + + struct sk_buff *skb; + struct atm_vcc *vcc; + + struct list_head entry; +}; + +#define TPD_ALIGNMENT 64 +#define TPD_LEN_MASK 0xffff + +#define TPD_ADDR_SHIFT 6 +#define TPD_MASK 0xffffffc0 +#define TPD_ADDR(x) ((x) & TPD_MASK) +#define TPD_INDEX(x) (TPD_ADDR(x) >> TPD_ADDR_SHIFT) + + +/* table 2.3 transmit buffer return elements */ + +struct he_tbrq { + volatile u32 tbre; +}; + +#define TBRQ_ALIGNMENT CONFIG_TBRQ_SIZE + +#define TBRQ_TPD(tbrq) ((tbrq)->tbre & 0xffffffc0) +#define TBRQ_EOS(tbrq) ((tbrq)->tbre & (1<<3)) +#define TBRQ_MULTIPLE(tbrq) ((tbrq)->tbre & (1)) + +/* table 2.21 receive buffer return queue element field organization */ + +struct he_rbrq { + volatile u32 addr; + volatile u32 cidlen; +}; + +#define RBRQ_ALIGNMENT CONFIG_RBRQ_SIZE + +#define RBRQ_ADDR(rbrq) ((rbrq)->addr & 0xffffffc0) +#define RBRQ_CRC_ERR(rbrq) ((rbrq)->addr & (1<<5)) +#define RBRQ_LEN_ERR(rbrq) ((rbrq)->addr & (1<<4)) +#define RBRQ_END_PDU(rbrq) ((rbrq)->addr & (1<<3)) +#define RBRQ_AAL5_PROT(rbrq) ((rbrq)->addr & (1<<2)) +#define RBRQ_CON_CLOSED(rbrq) ((rbrq)->addr & (1<<1)) +#define RBRQ_HBUF_ERR(rbrq) ((rbrq)->addr & 1) +#define RBRQ_CID(rbrq) (((rbrq)->cidlen >> 16) & 0x1fff) +#define RBRQ_BUFLEN(rbrq) ((rbrq)->cidlen & 0xffff) + +/* figure 2.3 transmit packet descriptor ready queue */ + +struct he_tpdrq { + volatile u32 tpd; + volatile u32 cid; +}; + +#define TPDRQ_ALIGNMENT CONFIG_TPDRQ_SIZE + +/* table 2.30 host status page detail */ + +#define HSP_ALIGNMENT 0x400 /* must align on 1k boundary */ + +struct he_hsp { + struct he_hsp_entry { + volatile u32 tbrq_tail; + volatile u32 reserved1[15]; + volatile u32 rbrq_tail; + volatile u32 reserved2[15]; + } group[HE_NUM_GROUPS]; +}; + +/* + * figure 2.9 receive buffer pools + * + * since a virtual address might be more than 32 bits, we store an index + * in the virt member of he_rbp. NOTE: the lower six bits in the rbrq + * addr member are used for buffer status further limiting us to 26 bits. + */ + +struct he_rbp { + volatile u32 phys; + volatile u32 idx; /* virt */ +}; + +#define RBP_IDX_OFFSET 6 + +/* + * the he dma engine will try to hold an extra 16 buffers in its local + * caches. and add a couple buffers for safety. + */ + +#define RBPL_TABLE_SIZE (CONFIG_RBPL_SIZE + 16 + 2) + +struct he_buff { + struct list_head entry; + dma_addr_t mapping; + unsigned long len; + u8 data[]; +}; + +#ifdef notyet +struct he_group { + u32 rpbl_size, rpbl_qsize; + struct he_rpb_entry *rbpl_ba; +}; +#endif + +#define HE_LOOKUP_VCC(dev, cid) ((dev)->he_vcc_table[(cid)].vcc) + +struct he_vcc_table +{ + struct atm_vcc *vcc; +}; + +struct he_cs_stper +{ + long pcr; + int inuse; +}; + +#define HE_NUM_CS_STPER 16 + +struct he_dev { + unsigned int number; + unsigned int irq; + void __iomem *membase; + + char prod_id[30]; + char mac_addr[6]; + int media; + + unsigned int vcibits, vpibits; + unsigned int cells_per_row; + unsigned int bytes_per_row; + unsigned int cells_per_lbuf; + unsigned int r0_numrows, r0_startrow, r0_numbuffs; + unsigned int r1_numrows, r1_startrow, r1_numbuffs; + unsigned int tx_numrows, tx_startrow, tx_numbuffs; + unsigned int buffer_limit; + + struct he_vcc_table *he_vcc_table; + +#ifdef notyet + struct he_group group[HE_NUM_GROUPS]; +#endif + struct he_cs_stper cs_stper[HE_NUM_CS_STPER]; + unsigned total_bw; + + dma_addr_t irq_phys; + struct he_irq *irq_base, *irq_head, *irq_tail; + volatile unsigned *irq_tailoffset; + int irq_peak; + + struct tasklet_struct tasklet; + struct dma_pool *tpd_pool; + struct list_head outstanding_tpds; + + dma_addr_t tpdrq_phys; + struct he_tpdrq *tpdrq_base, *tpdrq_tail, *tpdrq_head; + + spinlock_t global_lock; /* 8.1.5 pci transaction ordering + error problem */ + dma_addr_t rbrq_phys; + struct he_rbrq *rbrq_base, *rbrq_head; + int rbrq_peak; + + struct he_buff **rbpl_virt; + unsigned long *rbpl_table; + unsigned long rbpl_hint; + struct dma_pool *rbpl_pool; + dma_addr_t rbpl_phys; + struct he_rbp *rbpl_base, *rbpl_tail; + struct list_head rbpl_outstanding; + int rbpl_peak; + + dma_addr_t tbrq_phys; + struct he_tbrq *tbrq_base, *tbrq_head; + int tbrq_peak; + + dma_addr_t hsp_phys; + struct he_hsp *hsp; + + struct pci_dev *pci_dev; + struct atm_dev *atm_dev; + struct he_dev *next; +}; + +#define HE_MAXIOV 20 + +struct he_vcc +{ + struct list_head buffers; + int pdu_len; + int rc_index; + + wait_queue_head_t rx_waitq; + wait_queue_head_t tx_waitq; +}; + +#define HE_VCC(vcc) ((struct he_vcc *)(vcc->dev_data)) + +#define PCI_VENDOR_ID_FORE 0x1127 +#define PCI_DEVICE_ID_FORE_HE 0x400 + +#define GEN_CNTL_0 0x40 +#define INT_PROC_ENBL (1<<25) +#define SLAVE_ENDIAN_MODE (1<<16) +#define MRL_ENB (1<<5) +#define MRM_ENB (1<<4) +#define INIT_ENB (1<<2) +#define IGNORE_TIMEOUT (1<<1) +#define ENBL_64 (1<<0) + +#define MIN_PCI_LATENCY 32 /* errata 8.1.3 */ + +#define HE_DEV(dev) ((struct he_dev *) (dev)->dev_data) + +#define he_is622(dev) ((dev)->media & 0x1) +#define he_isMM(dev) ((dev)->media & 0x20) + +#define HE_REGMAP_SIZE 0x100000 + +#define RESET_CNTL 0x80000 +#define BOARD_RST_STATUS (1<<6) + +#define HOST_CNTL 0x80004 +#define PCI_BUS_SIZE64 (1<<27) +#define DESC_RD_STATIC_64 (1<<26) +#define DATA_RD_STATIC_64 (1<<25) +#define DATA_WR_STATIC_64 (1<<24) +#define ID_CS (1<<12) +#define ID_WREN (1<<11) +#define ID_DOUT (1<<10) +#define ID_DOFFSET 10 +#define ID_DIN (1<<9) +#define ID_CLOCK (1<<8) +#define QUICK_RD_RETRY (1<<7) +#define QUICK_WR_RETRY (1<<6) +#define OUTFF_ENB (1<<5) +#define CMDFF_ENB (1<<4) +#define PERR_INT_ENB (1<<2) +#define IGNORE_INTR (1<<0) + +#define LB_SWAP 0x80008 +#define SWAP_RNUM_MAX(x) (x<<27) +#define DATA_WR_SWAP (1<<20) +#define DESC_RD_SWAP (1<<19) +#define DATA_RD_SWAP (1<<18) +#define INTR_SWAP (1<<17) +#define DESC_WR_SWAP (1<<16) +#define SDRAM_INIT (1<<15) +#define BIG_ENDIAN_HOST (1<<14) +#define XFER_SIZE (1<<7) + +#define LB_MEM_ADDR 0x8000c +#define LB_MEM_DATA 0x80010 + +#define LB_MEM_ACCESS 0x80014 +#define LB_MEM_HNDSHK (1<<30) +#define LM_MEM_WRITE (0x7) +#define LM_MEM_READ (0x3) + +#define SDRAM_CTL 0x80018 +#define LB_64_ENB (1<<3) +#define LB_TWR (1<<2) +#define LB_TRP (1<<1) +#define LB_TRAS (1<<0) + +#define INT_FIFO 0x8001c +#define INT_MASK_D (1<<15) +#define INT_MASK_C (1<<14) +#define INT_MASK_B (1<<13) +#define INT_MASK_A (1<<12) +#define INT_CLEAR_D (1<<11) +#define INT_CLEAR_C (1<<10) +#define INT_CLEAR_B (1<<9) +#define INT_CLEAR_A (1<<8) + +#define ABORT_ADDR 0x80020 + +#define IRQ0_BASE 0x80080 +#define IRQ_BASE(x) (x<<12) +#define IRQ_MASK ((CONFIG_IRQ_SIZE<<2)-1) /* was 0x3ff */ +#define IRQ_TAIL(x) (((unsigned long)(x)) & IRQ_MASK) +#define IRQ0_HEAD 0x80084 +#define IRQ_SIZE(x) (x<<22) +#define IRQ_THRESH(x) (x<<12) +#define IRQ_HEAD(x) (x<<2) +/* #define IRQ_PENDING (1) conflict with linux/irq.h */ +#define IRQ0_CNTL 0x80088 +#define IRQ_ADDRSEL(x) (x<<2) +#define IRQ_INT_A (0<<2) +#define IRQ_INT_B (1<<2) +#define IRQ_INT_C (2<<2) +#define IRQ_INT_D (3<<2) +#define IRQ_TYPE_ADDR 0x1 +#define IRQ_TYPE_LINE 0x0 +#define IRQ0_DATA 0x8008c + +#define IRQ1_BASE 0x80090 +#define IRQ1_HEAD 0x80094 +#define IRQ1_CNTL 0x80098 +#define IRQ1_DATA 0x8009c + +#define IRQ2_BASE 0x800a0 +#define IRQ2_HEAD 0x800a4 +#define IRQ2_CNTL 0x800a8 +#define IRQ2_DATA 0x800ac + +#define IRQ3_BASE 0x800b0 +#define IRQ3_HEAD 0x800b4 +#define IRQ3_CNTL 0x800b8 +#define IRQ3_DATA 0x800bc + +#define GRP_10_MAP 0x800c0 +#define GRP_32_MAP 0x800c4 +#define GRP_54_MAP 0x800c8 +#define GRP_76_MAP 0x800cc + +#define G0_RBPS_S 0x80400 +#define G0_RBPS_T 0x80404 +#define RBP_TAIL(x) ((x)<<3) +#define RBP_MASK(x) ((x)|0x1fff) +#define G0_RBPS_QI 0x80408 +#define RBP_QSIZE(x) ((x)<<14) +#define RBP_INT_ENB (1<<13) +#define RBP_THRESH(x) (x) +#define G0_RBPS_BS 0x8040c +#define G0_RBPL_S 0x80410 +#define G0_RBPL_T 0x80414 +#define G0_RBPL_QI 0x80418 +#define G0_RBPL_BS 0x8041c + +#define G1_RBPS_S 0x80420 +#define G1_RBPS_T 0x80424 +#define G1_RBPS_QI 0x80428 +#define G1_RBPS_BS 0x8042c +#define G1_RBPL_S 0x80430 +#define G1_RBPL_T 0x80434 +#define G1_RBPL_QI 0x80438 +#define G1_RBPL_BS 0x8043c + +#define G2_RBPS_S 0x80440 +#define G2_RBPS_T 0x80444 +#define G2_RBPS_QI 0x80448 +#define G2_RBPS_BS 0x8044c +#define G2_RBPL_S 0x80450 +#define G2_RBPL_T 0x80454 +#define G2_RBPL_QI 0x80458 +#define G2_RBPL_BS 0x8045c + +#define G3_RBPS_S 0x80460 +#define G3_RBPS_T 0x80464 +#define G3_RBPS_QI 0x80468 +#define G3_RBPS_BS 0x8046c +#define G3_RBPL_S 0x80470 +#define G3_RBPL_T 0x80474 +#define G3_RBPL_QI 0x80478 +#define G3_RBPL_BS 0x8047c + +#define G4_RBPS_S 0x80480 +#define G4_RBPS_T 0x80484 +#define G4_RBPS_QI 0x80488 +#define G4_RBPS_BS 0x8048c +#define G4_RBPL_S 0x80490 +#define G4_RBPL_T 0x80494 +#define G4_RBPL_QI 0x80498 +#define G4_RBPL_BS 0x8049c + +#define G5_RBPS_S 0x804a0 +#define G5_RBPS_T 0x804a4 +#define G5_RBPS_QI 0x804a8 +#define G5_RBPS_BS 0x804ac +#define G5_RBPL_S 0x804b0 +#define G5_RBPL_T 0x804b4 +#define G5_RBPL_QI 0x804b8 +#define G5_RBPL_BS 0x804bc + +#define G6_RBPS_S 0x804c0 +#define G6_RBPS_T 0x804c4 +#define G6_RBPS_QI 0x804c8 +#define G6_RBPS_BS 0x804cc +#define G6_RBPL_S 0x804d0 +#define G6_RBPL_T 0x804d4 +#define G6_RBPL_QI 0x804d8 +#define G6_RBPL_BS 0x804dc + +#define G7_RBPS_S 0x804e0 +#define G7_RBPS_T 0x804e4 +#define G7_RBPS_QI 0x804e8 +#define G7_RBPS_BS 0x804ec + +#define G7_RBPL_S 0x804f0 +#define G7_RBPL_T 0x804f4 +#define G7_RBPL_QI 0x804f8 +#define G7_RBPL_BS 0x804fc + +#define G0_RBRQ_ST 0x80500 +#define G0_RBRQ_H 0x80504 +#define G0_RBRQ_Q 0x80508 +#define RBRQ_THRESH(x) ((x)<<13) +#define RBRQ_SIZE(x) (x) +#define G0_RBRQ_I 0x8050c +#define RBRQ_TIME(x) ((x)<<8) +#define RBRQ_COUNT(x) (x) + +/* fill in 1 ... 7 later */ + +#define G0_TBRQ_B_T 0x80600 +#define G0_TBRQ_H 0x80604 +#define G0_TBRQ_S 0x80608 +#define G0_TBRQ_THRESH 0x8060c +#define TBRQ_THRESH(x) (x) + +/* fill in 1 ... 7 later */ + +#define RH_CONFIG 0x805c0 +#define PHY_INT_ENB (1<<10) +#define OAM_GID(x) (x<<7) +#define PTMR_PRE(x) (x) + +#define G0_INMQ_S 0x80580 +#define G0_INMQ_L 0x80584 +#define G1_INMQ_S 0x80588 +#define G1_INMQ_L 0x8058c +#define G2_INMQ_S 0x80590 +#define G2_INMQ_L 0x80594 +#define G3_INMQ_S 0x80598 +#define G3_INMQ_L 0x8059c +#define G4_INMQ_S 0x805a0 +#define G4_INMQ_L 0x805a4 +#define G5_INMQ_S 0x805a8 +#define G5_INMQ_L 0x805ac +#define G6_INMQ_S 0x805b0 +#define G6_INMQ_L 0x805b4 +#define G7_INMQ_S 0x805b8 +#define G7_INMQ_L 0x805bc + +#define TPDRQ_B_H 0x80680 +#define TPDRQ_T 0x80684 +#define TPDRQ_S 0x80688 + +#define UBUFF_BA 0x8068c + +#define RLBF0_H 0x806c0 +#define RLBF0_T 0x806c4 +#define RLBF1_H 0x806c8 +#define RLBF1_T 0x806cc +#define RLBC_H 0x806d0 +#define RLBC_T 0x806d4 +#define RLBC_H2 0x806d8 +#define TLBF_H 0x806e0 +#define TLBF_T 0x806e4 +#define RLBF0_C 0x806e8 +#define RLBF1_C 0x806ec +#define RXTHRSH 0x806f0 +#define LITHRSH 0x806f4 + +#define LBARB 0x80700 +#define SLICE_X(x) (x<<28) +#define ARB_RNUM_MAX(x) (x<<23) +#define TH_PRTY(x) (x<<21) +#define RH_PRTY(x) (x<<19) +#define TL_PRTY(x) (x<<17) +#define RL_PRTY(x) (x<<15) +#define BUS_MULTI(x) (x<<8) +#define NET_PREF(x) (x) + +#define SDRAMCON 0x80704 +#define BANK_ON (1<<14) +#define WIDE_DATA (1<<13) +#define TWR_WAIT (1<<12) +#define TRP_WAIT (1<<11) +#define TRAS_WAIT (1<<10) +#define REF_RATE(x) (x) + +#define LBSTAT 0x80708 + +#define RCC_STAT 0x8070c +#define RCC_BUSY (1) + +#define TCMCONFIG 0x80740 +#define TM_DESL2 (1<<10) +#define TM_BANK_WAIT(x) (x<<6) +#define TM_ADD_BANK4(x) (x<<4) +#define TM_PAR_CHECK(x) (x<<3) +#define TM_RW_WAIT(x) (x<<2) +#define TM_SRAM_TYPE(x) (x) + +#define TSRB_BA 0x80744 +#define TSRC_BA 0x80748 +#define TMABR_BA 0x8074c +#define TPD_BA 0x80750 +#define TSRD_BA 0x80758 + +#define TX_CONFIG 0x80760 +#define DRF_THRESH(x) (x<<22) +#define TX_UT_MODE(x) (x<<21) +#define TX_VCI_MASK(x) (x<<17) +#define LBFREE_CNT(x) (x) + +#define TXAAL5_PROTO 0x80764 +#define CPCS_UU(x) (x<<8) +#define CPI(x) (x) + +#define RCMCONFIG 0x80780 +#define RM_DESL2(x) (x<<10) +#define RM_BANK_WAIT(x) (x<<6) +#define RM_ADD_BANK(x) (x<<4) +#define RM_PAR_CHECK(x) (x<<3) +#define RM_RW_WAIT(x) (x<<2) +#define RM_SRAM_TYPE(x) (x) + +#define RCMRSRB_BA 0x80784 +#define RCMLBM_BA 0x80788 +#define RCMABR_BA 0x8078c + +#define RC_CONFIG 0x807c0 +#define UT_RD_DELAY(x) (x<<11) +#define WRAP_MODE(x) (x<<10) +#define RC_UT_MODE(x) (x<<9) +#define RX_ENABLE (1<<8) +#define RX_VALVP(x) (x<<4) +#define RX_VALVC(x) (x) + +#define MCC 0x807c4 +#define OEC 0x807c8 +#define DCC 0x807cc +#define CEC 0x807d0 + +#define HSP_BA 0x807f0 + +#define LB_CONFIG 0x807f4 +#define LB_SIZE(x) (x) + +#define CON_DAT 0x807f8 +#define CON_CTL 0x807fc +#define CON_CTL_MBOX (2<<30) +#define CON_CTL_TCM (1<<30) +#define CON_CTL_RCM (0<<30) +#define CON_CTL_WRITE (1<<29) +#define CON_CTL_READ (0<<29) +#define CON_CTL_BUSY (1<<28) +#define CON_BYTE_DISABLE_3 (1<<22) /* 24..31 */ +#define CON_BYTE_DISABLE_2 (1<<21) /* 16..23 */ +#define CON_BYTE_DISABLE_1 (1<<20) /* 8..15 */ +#define CON_BYTE_DISABLE_0 (1<<19) /* 0..7 */ +#define CON_CTL_ADDR(x) (x) + +#define FRAMER 0x80800 /* to 0x80bfc */ + +/* 3.3 network controller (internal) mailbox registers */ + +#define CS_STPER0 0x0 + /* ... */ +#define CS_STPER31 0x01f + +#define CS_STTIM0 0x020 + /* ... */ +#define CS_STTIM31 0x03f + +#define CS_TGRLD0 0x040 + /* ... */ +#define CS_TGRLD15 0x04f + +#define CS_ERTHR0 0x050 +#define CS_ERTHR1 0x051 +#define CS_ERTHR2 0x052 +#define CS_ERTHR3 0x053 +#define CS_ERTHR4 0x054 +#define CS_ERCTL0 0x055 +#define TX_ENABLE (1<<28) +#define ER_ENABLE (1<<27) +#define CS_ERCTL1 0x056 +#define CS_ERCTL2 0x057 +#define CS_ERSTAT0 0x058 +#define CS_ERSTAT1 0x059 + +#define CS_RTCCT 0x060 +#define CS_RTFWC 0x061 +#define CS_RTFWR 0x062 +#define CS_RTFTC 0x063 +#define CS_RTATR 0x064 + +#define CS_TFBSET 0x070 +#define CS_TFBADD 0x071 +#define CS_TFBSUB 0x072 +#define CS_WCRMAX 0x073 +#define CS_WCRMIN 0x074 +#define CS_WCRINC 0x075 +#define CS_WCRDEC 0x076 +#define CS_WCRCEIL 0x077 +#define CS_BWDCNT 0x078 + +#define CS_OTPPER 0x080 +#define CS_OTWPER 0x081 +#define CS_OTTLIM 0x082 +#define CS_OTTCNT 0x083 + +#define CS_HGRRT0 0x090 + /* ... */ +#define CS_HGRRT7 0x097 + +#define CS_ORPTRS 0x0a0 + +#define RXCON_CLOSE 0x100 + + +#define RCM_MEM_SIZE 0x10000 /* 1M of 32-bit registers */ +#define TCM_MEM_SIZE 0x20000 /* 2M of 32-bit registers */ + +/* 2.5 transmit connection memory registers */ + +#define TSR0_CONN_STATE(x) ((x>>28) & 0x7) +#define TSR0_USE_WMIN (1<<23) +#define TSR0_GROUP(x) ((x & 0x7)<<18) +#define TSR0_ABR (2<<16) +#define TSR0_UBR (1<<16) +#define TSR0_CBR (0<<16) +#define TSR0_PROT (1<<15) +#define TSR0_AAL0_SDU (2<<12) +#define TSR0_AAL0 (1<<12) +#define TSR0_AAL5 (0<<12) +#define TSR0_HALT_ER (1<<11) +#define TSR0_MARK_CI (1<<10) +#define TSR0_MARK_ER (1<<9) +#define TSR0_UPDATE_GER (1<<8) +#define TSR0_RC_INDEX(x) (x & 0x1F) + +#define TSR1_PCR(x) ((x & 0x7FFF)<<16) +#define TSR1_MCR(x) (x & 0x7FFF) + +#define TSR2_ACR(x) ((x & 0x7FFF)<<16) + +#define TSR3_NRM_CNT(x) ((x & 0xFF)<<24) +#define TSR3_CRM_CNT(x) (x & 0xFFFF) + +#define TSR4_FLUSH_CONN (1<<31) +#define TSR4_SESSION_ENDED (1<<30) +#define TSR4_CRC10 (1<<28) +#define TSR4_NULL_CRC10 (1<<27) +#define TSR4_PROT (1<<26) +#define TSR4_AAL0_SDU (2<<23) +#define TSR4_AAL0 (1<<23) +#define TSR4_AAL5 (0<<23) + +#define TSR9_OPEN_CONN (1<<20) + +#define TSR11_ICR(x) ((x & 0x7FFF)<<16) +#define TSR11_TRM(x) ((x & 0x7)<<13) +#define TSR11_NRM(x) ((x & 0x7)<<10) +#define TSR11_ADTF(x) (x & 0x3FF) + +#define TSR13_RDF(x) ((x & 0xF)<<23) +#define TSR13_RIF(x) ((x & 0xF)<<19) +#define TSR13_CDF(x) ((x & 0x7)<<16) +#define TSR13_CRM(x) (x & 0xFFFF) + +#define TSR14_DELETE (1<<31) +#define TSR14_ABR_CLOSE (1<<16) + +/* 2.7.1 per connection receieve state registers */ + +#define RSR0_START_PDU (1<<10) +#define RSR0_OPEN_CONN (1<<6) +#define RSR0_CLOSE_CONN (0<<6) +#define RSR0_PPD_ENABLE (1<<5) +#define RSR0_EPD_ENABLE (1<<4) +#define RSR0_TCP_CKSUM (1<<3) +#define RSR0_AAL5 (0) +#define RSR0_AAL0 (1) +#define RSR0_AAL0_SDU (2) +#define RSR0_RAWCELL (3) +#define RSR0_RAWCELL_CRC10 (4) + +#define RSR1_AQI_ENABLE (1<<20) +#define RSR1_RBPL_ONLY (1<<19) +#define RSR1_GROUP(x) ((x)<<16) + +#define RSR4_AQI_ENABLE (1<<30) +#define RSR4_GROUP(x) ((x)<<27) +#define RSR4_RBPL_ONLY (1<<26) + +/* 2.1.4 transmit packet descriptor */ + +#define TPD_USERCELL 0x0 +#define TPD_SEGMENT_OAMF5 0x4 +#define TPD_END2END_OAMF5 0x5 +#define TPD_RMCELL 0x6 +#define TPD_CELLTYPE(x) (x<<3) +#define TPD_EOS (1<<2) +#define TPD_CLP (1<<1) +#define TPD_INT (1<<0) +#define TPD_LST (1<<31) + +/* table 4.3 serial eeprom information */ + +#define PROD_ID 0x08 /* char[] */ +#define PROD_ID_LEN 30 +#define HW_REV 0x26 /* char[] */ +#define M_SN 0x3a /* integer */ +#define MEDIA 0x3e /* integer */ +#define HE155MM 0x26 +#define HE622MM 0x27 +#define HE155SM 0x46 +#define HE622SM 0x47 +#define MAC_ADDR 0x42 /* char[] */ + +#define CS_LOW 0x0 +#define CS_HIGH ID_CS /* HOST_CNTL_ID_PROM_SEL */ +#define CLK_LOW 0x0 +#define CLK_HIGH ID_CLOCK /* HOST_CNTL_ID_PROM_CLOCK */ +#define SI_HIGH ID_DIN /* HOST_CNTL_ID_PROM_DATA_IN */ +#define EEPROM_DELAY 400 /* microseconds */ + +#endif /* _HE_H_ */ diff --git a/drivers/atm/horizon.c b/drivers/atm/horizon.c new file mode 100644 index 000000000..4f2951cbe --- /dev/null +++ b/drivers/atm/horizon.c @@ -0,0 +1,2853 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Madge Horizon ATM Adapter driver. + Copyright (C) 1995-1999 Madge Networks Ltd. + +*/ + +/* + IMPORTANT NOTE: Madge Networks no longer makes the adapters + supported by this driver and makes no commitment to maintain it. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "horizon.h" + +#define maintainer_string "Giuliano Procida at Madge Networks " +#define description_string "Madge ATM Horizon [Ultra] driver" +#define version_string "1.2.1" + +static inline void __init show_version (void) { + printk ("%s version %s\n", description_string, version_string); +} + +/* + + CREDITS + + Driver and documentation by: + + Chris Aston Madge Networks + Giuliano Procida Madge Networks + Simon Benham Madge Networks + Simon Johnson Madge Networks + Various Others Madge Networks + + Some inspiration taken from other drivers by: + + Alexandru Cucos UTBv + Kari Mettinen University of Helsinki + Werner Almesberger EPFL LRC + + Theory of Operation + + I Hardware, detection, initialisation and shutdown. + + 1. Supported Hardware + + This driver should handle all variants of the PCI Madge ATM adapters + with the Horizon chipset. These are all PCI cards supporting PIO, BM + DMA and a form of MMIO (registers only, not internal RAM). + + The driver is only known to work with SONET and UTP Horizon Ultra + cards at 155Mb/s. However, code is in place to deal with both the + original Horizon and 25Mb/s operation. + + There are two revisions of the Horizon ASIC: the original and the + Ultra. Details of hardware bugs are in section III. + + The ASIC version can be distinguished by chip markings but is NOT + indicated by the PCI revision (all adapters seem to have PCI rev 1). + + I believe that: + + Horizon => Collage 25 PCI Adapter (UTP and STP) + Horizon Ultra => Collage 155 PCI Client (UTP or SONET) + Ambassador x => Collage 155 PCI Server (completely different) + + Horizon (25Mb/s) is fitted with UTP and STP connectors. It seems to + have a Madge B154 plus glue logic serializer. I have also found a + really ancient version of this with slightly different glue. It + comes with the revision 0 (140-025-01) ASIC. + + Horizon Ultra (155Mb/s) is fitted with either a Pulse Medialink + output (UTP) or an HP HFBR 5205 output (SONET). It has either + Madge's SAMBA framer or a SUNI-lite device (early versions). It + comes with the revision 1 (140-027-01) ASIC. + + 2. Detection + + All Horizon-based cards present with the same PCI Vendor and Device + IDs. The standard Linux 2.2 PCI API is used to locate any cards and + to enable bus-mastering (with appropriate latency). + + ATM_LAYER_STATUS in the control register distinguishes between the + two possible physical layers (25 and 155). It is not clear whether + the 155 cards can also operate at 25Mbps. We rely on the fact that a + card operates at 155 if and only if it has the newer Horizon Ultra + ASIC. + + For 155 cards the two possible framers are probed for and then set + up for loop-timing. + + 3. Initialisation + + The card is reset and then put into a known state. The physical + layer is configured for normal operation at the appropriate speed; + in the case of the 155 cards, the framer is initialised with + line-based timing; the internal RAM is zeroed and the allocation of + buffers for RX and TX is made; the Burnt In Address is read and + copied to the ATM ESI; various policy settings for RX (VPI bits, + unknown VCs, oam cells) are made. Ideally all policy items should be + configurable at module load (if not actually on-demand), however, + only the vpi vs vci bit allocation can be specified at insmod. + + 4. Shutdown + + This is in response to module_cleaup. No VCs are in use and the card + should be idle; it is reset. + + II Driver software (as it should be) + + 0. Traffic Parameters + + The traffic classes (not an enumeration) are currently: ATM_NONE (no + traffic), ATM_UBR, ATM_CBR, ATM_VBR and ATM_ABR, ATM_ANYCLASS + (compatible with everything). Together with (perhaps only some of) + the following items they make up the traffic specification. + + struct atm_trafprm { + unsigned char traffic_class; traffic class (ATM_UBR, ...) + int max_pcr; maximum PCR in cells per second + int pcr; desired PCR in cells per second + int min_pcr; minimum PCR in cells per second + int max_cdv; maximum CDV in microseconds + int max_sdu; maximum SDU in bytes + }; + + Note that these denote bandwidth available not bandwidth used; the + possibilities according to ATMF are: + + Real Time (cdv and max CDT given) + + CBR(pcr) pcr bandwidth always available + rtVBR(pcr,scr,mbs) scr bandwidth always available, up to pcr at mbs too + + Non Real Time + + nrtVBR(pcr,scr,mbs) scr bandwidth always available, up to pcr at mbs too + UBR() + ABR(mcr,pcr) mcr bandwidth always available, up to pcr (depending) too + + mbs is max burst size (bucket) + pcr and scr have associated cdvt values + mcr is like scr but has no cdtv + cdtv may differ at each hop + + Some of the above items are qos items (as opposed to traffic + parameters). We have nothing to do with qos. All except ABR can have + their traffic parameters converted to GCRA parameters. The GCRA may + be implemented as a (real-number) leaky bucket. The GCRA can be used + in complicated ways by switches and in simpler ways by end-stations. + It can be used both to filter incoming cells and shape out-going + cells. + + ATM Linux actually supports: + + ATM_NONE() (no traffic in this direction) + ATM_UBR(max_frame_size) + ATM_CBR(max/min_pcr, max_cdv, max_frame_size) + + 0 or ATM_MAX_PCR are used to indicate maximum available PCR + + A traffic specification consists of the AAL type and separate + traffic specifications for either direction. In ATM Linux it is: + + struct atm_qos { + struct atm_trafprm txtp; + struct atm_trafprm rxtp; + unsigned char aal; + }; + + AAL types are: + + ATM_NO_AAL AAL not specified + ATM_AAL0 "raw" ATM cells + ATM_AAL1 AAL1 (CBR) + ATM_AAL2 AAL2 (VBR) + ATM_AAL34 AAL3/4 (data) + ATM_AAL5 AAL5 (data) + ATM_SAAL signaling AAL + + The Horizon has support for AAL frame types: 0, 3/4 and 5. However, + it does not implement AAL 3/4 SAR and it has a different notion of + "raw cell" to ATM Linux's (48 bytes vs. 52 bytes) so neither are + supported by this driver. + + The Horizon has limited support for ABR (including UBR), VBR and + CBR. Each TX channel has a bucket (containing up to 31 cell units) + and two timers (PCR and SCR) associated with it that can be used to + govern cell emissions and host notification (in the case of ABR this + is presumably so that RM cells may be emitted at appropriate times). + The timers may either be disabled or may be set to any of 240 values + (determined by the clock crystal, a fixed (?) per-device divider, a + configurable divider and a configurable timer preload value). + + At the moment only UBR and CBR are supported by the driver. VBR will + be supported as soon as ATM for Linux supports it. ABR support is + very unlikely as RM cell handling is completely up to the driver. + + 1. TX (TX channel setup and TX transfer) + + The TX half of the driver owns the TX Horizon registers. The TX + component in the IRQ handler is the BM completion handler. This can + only be entered when tx_busy is true (enforced by hardware). The + other TX component can only be entered when tx_busy is false + (enforced by driver). So TX is single-threaded. + + Apart from a minor optimisation to not re-select the last channel, + the TX send component works as follows: + + Atomic test and set tx_busy until we succeed; we should implement + some sort of timeout so that tx_busy will never be stuck at true. + + If no TX channel is set up for this VC we wait for an idle one (if + necessary) and set it up. + + At this point we have a TX channel ready for use. We wait for enough + buffers to become available then start a TX transmit (set the TX + descriptor, schedule transfer, exit). + + The IRQ component handles TX completion (stats, free buffer, tx_busy + unset, exit). We also re-schedule further transfers for the same + frame if needed. + + TX setup in more detail: + + TX open is a nop, the relevant information is held in the hrz_vcc + (vcc->dev_data) structure and is "cached" on the card. + + TX close gets the TX lock and clears the channel from the "cache". + + 2. RX (Data Available and RX transfer) + + The RX half of the driver owns the RX registers. There are two RX + components in the IRQ handler: the data available handler deals with + fresh data that has arrived on the card, the BM completion handler + is very similar to the TX completion handler. The data available + handler grabs the rx_lock and it is only released once the data has + been discarded or completely transferred to the host. The BM + completion handler only runs when the lock is held; the data + available handler is locked out over the same period. + + Data available on the card triggers an interrupt. If the data is not + suitable for our existing RX channels or we cannot allocate a buffer + it is flushed. Otherwise an RX receive is scheduled. Multiple RX + transfers may be scheduled for the same frame. + + RX setup in more detail: + + RX open... + RX close... + + III Hardware Bugs + + 0. Byte vs Word addressing of adapter RAM. + + A design feature; see the .h file (especially the memory map). + + 1. Bus Master Data Transfers (original Horizon only, fixed in Ultra) + + The host must not start a transmit direction transfer at a + non-four-byte boundary in host memory. Instead the host should + perform a byte, or a two byte, or one byte followed by two byte + transfer in order to start the rest of the transfer on a four byte + boundary. RX is OK. + + Simultaneous transmit and receive direction bus master transfers are + not allowed. + + The simplest solution to these two is to always do PIO (never DMA) + in the TX direction on the original Horizon. More complicated + solutions are likely to hurt my brain. + + 2. Loss of buffer on close VC + + When a VC is being closed, the buffer associated with it is not + returned to the pool. The host must store the reference to this + buffer and when opening a new VC then give it to that new VC. + + The host intervention currently consists of stacking such a buffer + pointer at VC close and checking the stack at VC open. + + 3. Failure to close a VC + + If a VC is currently receiving a frame then closing the VC may fail + and the frame continues to be received. + + The solution is to make sure any received frames are flushed when + ready. This is currently done just before the solution to 2. + + 4. PCI bus (original Horizon only, fixed in Ultra) + + Reading from the data port prior to initialisation will hang the PCI + bus. Just don't do that then! We don't. + + IV To Do List + + . Timer code may be broken. + + . Allow users to specify buffer allocation split for TX and RX. + + . Deal once and for all with buggy VC close. + + . Handle interrupted and/or non-blocking operations. + + . Change some macros to functions and move from .h to .c. + + . Try to limit the number of TX frames each VC may have queued, in + order to reduce the chances of TX buffer exhaustion. + + . Implement VBR (bucket and timers not understood) and ABR (need to + do RM cells manually); also no Linux support for either. + + . Implement QoS changes on open VCs (involves extracting parts of VC open + and close into separate functions and using them to make changes). + +*/ + +/********** globals **********/ + +static void do_housekeeping (struct timer_list *t); + +static unsigned short debug = 0; +static unsigned short vpi_bits = 0; +static int max_tx_size = 9000; +static int max_rx_size = 9000; +static unsigned char pci_lat = 0; + +/********** access functions **********/ + +/* Read / Write Horizon registers */ +static inline void wr_regl (const hrz_dev * dev, unsigned char reg, u32 data) { + outl (cpu_to_le32 (data), dev->iobase + reg); +} + +static inline u32 rd_regl (const hrz_dev * dev, unsigned char reg) { + return le32_to_cpu (inl (dev->iobase + reg)); +} + +static inline void wr_regw (const hrz_dev * dev, unsigned char reg, u16 data) { + outw (cpu_to_le16 (data), dev->iobase + reg); +} + +static inline u16 rd_regw (const hrz_dev * dev, unsigned char reg) { + return le16_to_cpu (inw (dev->iobase + reg)); +} + +static inline void wrs_regb (const hrz_dev * dev, unsigned char reg, void * addr, u32 len) { + outsb (dev->iobase + reg, addr, len); +} + +static inline void rds_regb (const hrz_dev * dev, unsigned char reg, void * addr, u32 len) { + insb (dev->iobase + reg, addr, len); +} + +/* Read / Write to a given address in Horizon buffer memory. + Interrupts must be disabled between the address register and data + port accesses as these must form an atomic operation. */ +static inline void wr_mem (const hrz_dev * dev, HDW * addr, u32 data) { + // wr_regl (dev, MEM_WR_ADDR_REG_OFF, (u32) addr); + wr_regl (dev, MEM_WR_ADDR_REG_OFF, (addr - (HDW *) 0) * sizeof(HDW)); + wr_regl (dev, MEMORY_PORT_OFF, data); +} + +static inline u32 rd_mem (const hrz_dev * dev, HDW * addr) { + // wr_regl (dev, MEM_RD_ADDR_REG_OFF, (u32) addr); + wr_regl (dev, MEM_RD_ADDR_REG_OFF, (addr - (HDW *) 0) * sizeof(HDW)); + return rd_regl (dev, MEMORY_PORT_OFF); +} + +static inline void wr_framer (const hrz_dev * dev, u32 addr, u32 data) { + wr_regl (dev, MEM_WR_ADDR_REG_OFF, (u32) addr | 0x80000000); + wr_regl (dev, MEMORY_PORT_OFF, data); +} + +static inline u32 rd_framer (const hrz_dev * dev, u32 addr) { + wr_regl (dev, MEM_RD_ADDR_REG_OFF, (u32) addr | 0x80000000); + return rd_regl (dev, MEMORY_PORT_OFF); +} + +/********** specialised access functions **********/ + +/* RX */ + +static inline void FLUSH_RX_CHANNEL (hrz_dev * dev, u16 channel) { + wr_regw (dev, RX_CHANNEL_PORT_OFF, FLUSH_CHANNEL | channel); + return; +} + +static void WAIT_FLUSH_RX_COMPLETE (hrz_dev * dev) { + while (rd_regw (dev, RX_CHANNEL_PORT_OFF) & FLUSH_CHANNEL) + ; + return; +} + +static inline void SELECT_RX_CHANNEL (hrz_dev * dev, u16 channel) { + wr_regw (dev, RX_CHANNEL_PORT_OFF, channel); + return; +} + +static void WAIT_UPDATE_COMPLETE (hrz_dev * dev) { + while (rd_regw (dev, RX_CHANNEL_PORT_OFF) & RX_CHANNEL_UPDATE_IN_PROGRESS) + ; + return; +} + +/* TX */ + +static inline void SELECT_TX_CHANNEL (hrz_dev * dev, u16 tx_channel) { + wr_regl (dev, TX_CHANNEL_PORT_OFF, tx_channel); + return; +} + +/* Update or query one configuration parameter of a particular channel. */ + +static inline void update_tx_channel_config (hrz_dev * dev, short chan, u8 mode, u16 value) { + wr_regw (dev, TX_CHANNEL_CONFIG_COMMAND_OFF, + chan * TX_CHANNEL_CONFIG_MULT | mode); + wr_regw (dev, TX_CHANNEL_CONFIG_DATA_OFF, value); + return; +} + +/********** dump functions **********/ + +static inline void dump_skb (char * prefix, unsigned int vc, struct sk_buff * skb) { +#ifdef DEBUG_HORIZON + unsigned int i; + unsigned char * data = skb->data; + PRINTDB (DBG_DATA, "%s(%u) ", prefix, vc); + for (i=0; ilen && i < 256;i++) + PRINTDM (DBG_DATA, "%02x ", data[i]); + PRINTDE (DBG_DATA,""); +#else + (void) prefix; + (void) vc; + (void) skb; +#endif + return; +} + +static inline void dump_regs (hrz_dev * dev) { +#ifdef DEBUG_HORIZON + PRINTD (DBG_REGS, "CONTROL 0: %#x", rd_regl (dev, CONTROL_0_REG)); + PRINTD (DBG_REGS, "RX CONFIG: %#x", rd_regw (dev, RX_CONFIG_OFF)); + PRINTD (DBG_REGS, "TX CONFIG: %#x", rd_regw (dev, TX_CONFIG_OFF)); + PRINTD (DBG_REGS, "TX STATUS: %#x", rd_regw (dev, TX_STATUS_OFF)); + PRINTD (DBG_REGS, "IRQ ENBLE: %#x", rd_regl (dev, INT_ENABLE_REG_OFF)); + PRINTD (DBG_REGS, "IRQ SORCE: %#x", rd_regl (dev, INT_SOURCE_REG_OFF)); +#else + (void) dev; +#endif + return; +} + +static inline void dump_framer (hrz_dev * dev) { +#ifdef DEBUG_HORIZON + unsigned int i; + PRINTDB (DBG_REGS, "framer registers:"); + for (i = 0; i < 0x10; ++i) + PRINTDM (DBG_REGS, " %02x", rd_framer (dev, i)); + PRINTDE (DBG_REGS,""); +#else + (void) dev; +#endif + return; +} + +/********** VPI/VCI <-> (RX) channel conversions **********/ + +/* RX channels are 10 bit integers, these fns are quite paranoid */ + +static inline int vpivci_to_channel (u16 * channel, const short vpi, const int vci) { + unsigned short vci_bits = 10 - vpi_bits; + if (0 <= vpi && vpi < 1<>RX_Q_ENTRY_CHANNEL_SHIFT) & RX_CHANNEL_MASK; +} + +/* Cell Transmit Rate Values + * + * the cell transmit rate (cells per sec) can be set to a variety of + * different values by specifying two parameters: a timer preload from + * 1 to 16 (stored as 0 to 15) and a clock divider (2 to the power of + * an exponent from 0 to 14; the special value 15 disables the timer). + * + * cellrate = baserate / (preload * 2^divider) + * + * The maximum cell rate that can be specified is therefore just the + * base rate. Halving the preload is equivalent to adding 1 to the + * divider and so values 1 to 8 of the preload are redundant except + * in the case of a maximal divider (14). + * + * Given a desired cell rate, an algorithm to determine the preload + * and divider is: + * + * a) x = baserate / cellrate, want p * 2^d = x (as far as possible) + * b) if x > 16 * 2^14 then set p = 16, d = 14 (min rate), done + * if x <= 16 then set p = x, d = 0 (high rates), done + * c) now have 16 < x <= 2^18, or 1 < x/16 <= 2^14 and we want to + * know n such that 2^(n-1) < x/16 <= 2^n, so slide a bit until + * we find the range (n will be between 1 and 14), set d = n + * d) Also have 8 < x/2^n <= 16, so set p nearest x/2^n + * + * The algorithm used below is a minor variant of the above. + * + * The base rate is derived from the oscillator frequency (Hz) using a + * fixed divider: + * + * baserate = freq / 32 in the case of some Unknown Card + * baserate = freq / 8 in the case of the Horizon 25 + * baserate = freq / 8 in the case of the Horizon Ultra 155 + * + * The Horizon cards have oscillators and base rates as follows: + * + * Card Oscillator Base Rate + * Unknown Card 33 MHz 1.03125 MHz (33 MHz = PCI freq) + * Horizon 25 32 MHz 4 MHz + * Horizon Ultra 155 40 MHz 5 MHz + * + * The following defines give the base rates in Hz. These were + * previously a factor of 100 larger, no doubt someone was using + * cps*100. + */ + +#define BR_UKN 1031250l +#define BR_HRZ 4000000l +#define BR_ULT 5000000l + +// d is an exponent +#define CR_MIND 0 +#define CR_MAXD 14 + +// p ranges from 1 to a power of 2 +#define CR_MAXPEXP 4 + +static int make_rate (const hrz_dev * dev, u32 c, rounding r, + u16 * bits, unsigned int * actual) +{ + // note: rounding the rate down means rounding 'p' up + const unsigned long br = test_bit(ultra, &dev->flags) ? BR_ULT : BR_HRZ; + + u32 div = CR_MIND; + u32 pre; + + // br_exp and br_man are used to avoid overflowing (c*maxp*2^d) in + // the tests below. We could think harder about exact possibilities + // of failure... + + unsigned long br_man = br; + unsigned int br_exp = 0; + + PRINTD (DBG_QOS|DBG_FLOW, "make_rate b=%lu, c=%u, %s", br, c, + r == round_up ? "up" : r == round_down ? "down" : "nearest"); + + // avoid div by zero + if (!c) { + PRINTD (DBG_QOS|DBG_ERR, "zero rate is not allowed!"); + return -EINVAL; + } + + while (br_exp < CR_MAXPEXP + CR_MIND && (br_man % 2 == 0)) { + br_man = br_man >> 1; + ++br_exp; + } + // (br >>br_exp) < CR_MAXD || (!pre) || pre > 1<rx_descs[channel]; + + PRINTD (DBG_FLOW, "hrz_open_rx %x", channel); + + spin_lock_irqsave (&dev->mem_lock, flags); + channel_type = rd_mem (dev, &rx_desc->wr_buf_type) & BUFFER_PTR_MASK; + spin_unlock_irqrestore (&dev->mem_lock, flags); + + // very serious error, should never occur + if (channel_type != RX_CHANNEL_DISABLED) { + PRINTD (DBG_ERR|DBG_VCC, "RX channel for VC already open"); + return -EBUSY; // clean up? + } + + // Give back spare buffer + if (dev->noof_spare_buffers) { + buf_ptr = dev->spare_buffers[--dev->noof_spare_buffers]; + PRINTD (DBG_VCC, "using a spare buffer: %u", buf_ptr); + // should never occur + if (buf_ptr == RX_CHANNEL_DISABLED || buf_ptr == RX_CHANNEL_IDLE) { + // but easy to recover from + PRINTD (DBG_ERR|DBG_VCC, "bad spare buffer pointer, using IDLE"); + buf_ptr = RX_CHANNEL_IDLE; + } + } else { + PRINTD (DBG_VCC, "using IDLE buffer pointer"); + } + + // Channel is currently disabled so change its status to idle + + // do we really need to save the flags again? + spin_lock_irqsave (&dev->mem_lock, flags); + + wr_mem (dev, &rx_desc->wr_buf_type, + buf_ptr | CHANNEL_TYPE_AAL5 | FIRST_CELL_OF_AAL5_FRAME); + if (buf_ptr != RX_CHANNEL_IDLE) + wr_mem (dev, &rx_desc->rd_buf_type, buf_ptr); + + spin_unlock_irqrestore (&dev->mem_lock, flags); + + // rxer->rate = make_rate (qos->peak_cells); + + PRINTD (DBG_FLOW, "hrz_open_rx ok"); + + return 0; +} + +#if 0 +/********** change vc rate for a given vc **********/ + +static void hrz_change_vc_qos (ATM_RXER * rxer, MAAL_QOS * qos) { + rxer->rate = make_rate (qos->peak_cells); +} +#endif + +/********** free an skb (as per ATM device driver documentation) **********/ + +static void hrz_kfree_skb (struct sk_buff * skb) { + if (ATM_SKB(skb)->vcc->pop) { + ATM_SKB(skb)->vcc->pop (ATM_SKB(skb)->vcc, skb); + } else { + dev_kfree_skb_any (skb); + } +} + +/********** cancel listen on a VC **********/ + +static void hrz_close_rx (hrz_dev * dev, u16 vc) { + unsigned long flags; + + u32 value; + + u32 r1, r2; + + rx_ch_desc * rx_desc = &memmap->rx_descs[vc]; + + int was_idle = 0; + + spin_lock_irqsave (&dev->mem_lock, flags); + value = rd_mem (dev, &rx_desc->wr_buf_type) & BUFFER_PTR_MASK; + spin_unlock_irqrestore (&dev->mem_lock, flags); + + if (value == RX_CHANNEL_DISABLED) { + // I suppose this could happen once we deal with _NONE traffic properly + PRINTD (DBG_VCC, "closing VC: RX channel %u already disabled", vc); + return; + } + if (value == RX_CHANNEL_IDLE) + was_idle = 1; + + spin_lock_irqsave (&dev->mem_lock, flags); + + for (;;) { + wr_mem (dev, &rx_desc->wr_buf_type, RX_CHANNEL_DISABLED); + + if ((rd_mem (dev, &rx_desc->wr_buf_type) & BUFFER_PTR_MASK) == RX_CHANNEL_DISABLED) + break; + + was_idle = 0; + } + + if (was_idle) { + spin_unlock_irqrestore (&dev->mem_lock, flags); + return; + } + + WAIT_FLUSH_RX_COMPLETE(dev); + + // XXX Is this all really necessary? We can rely on the rx_data_av + // handler to discard frames that remain queued for delivery. If the + // worry is that immediately reopening the channel (perhaps by a + // different process) may cause some data to be mis-delivered then + // there may still be a simpler solution (such as busy-waiting on + // rx_busy once the channel is disabled or before a new one is + // opened - does this leave any holes?). Arguably setting up and + // tearing down the TX and RX halves of each virtual circuit could + // most safely be done within ?x_busy protected regions. + + // OK, current changes are that Simon's marker is disabled and we DO + // look for NULL rxer elsewhere. The code here seems flush frames + // and then remember the last dead cell belonging to the channel + // just disabled - the cell gets relinked at the next vc_open. + // However, when all VCs are closed or only a few opened there are a + // handful of buffers that are unusable. + + // Does anyone feel like documenting spare_buffers properly? + // Does anyone feel like fixing this in a nicer way? + + // Flush any data which is left in the channel + for (;;) { + // Change the rx channel port to something different to the RX + // channel we are trying to close to force Horizon to flush the rx + // channel read and write pointers. + + u16 other = vc^(RX_CHANS/2); + + SELECT_RX_CHANNEL (dev, other); + WAIT_UPDATE_COMPLETE (dev); + + r1 = rd_mem (dev, &rx_desc->rd_buf_type); + + // Select this RX channel. Flush doesn't seem to work unless we + // select an RX channel before hand + + SELECT_RX_CHANNEL (dev, vc); + WAIT_UPDATE_COMPLETE (dev); + + // Attempt to flush a frame on this RX channel + + FLUSH_RX_CHANNEL (dev, vc); + WAIT_FLUSH_RX_COMPLETE (dev); + + // Force Horizon to flush rx channel read and write pointers as before + + SELECT_RX_CHANNEL (dev, other); + WAIT_UPDATE_COMPLETE (dev); + + r2 = rd_mem (dev, &rx_desc->rd_buf_type); + + PRINTD (DBG_VCC|DBG_RX, "r1 = %u, r2 = %u", r1, r2); + + if (r1 == r2) { + dev->spare_buffers[dev->noof_spare_buffers++] = (u16)r1; + break; + } + } + +#if 0 + { + rx_q_entry * wr_ptr = &memmap->rx_q_entries[rd_regw (dev, RX_QUEUE_WR_PTR_OFF)]; + rx_q_entry * rd_ptr = dev->rx_q_entry; + + PRINTD (DBG_VCC|DBG_RX, "rd_ptr = %u, wr_ptr = %u", rd_ptr, wr_ptr); + + while (rd_ptr != wr_ptr) { + u32 x = rd_mem (dev, (HDW *) rd_ptr); + + if (vc == rx_q_entry_to_rx_channel (x)) { + x |= SIMONS_DODGEY_MARKER; + + PRINTD (DBG_RX|DBG_VCC|DBG_WARN, "marking a frame as dodgey"); + + wr_mem (dev, (HDW *) rd_ptr, x); + } + + if (rd_ptr == dev->rx_q_wrap) + rd_ptr = dev->rx_q_reset; + else + rd_ptr++; + } + } +#endif + + spin_unlock_irqrestore (&dev->mem_lock, flags); + + return; +} + +/********** schedule RX transfers **********/ + +// Note on tail recursion: a GCC developer said that it is not likely +// to be fixed soon, so do not define TAILRECUSRIONWORKS unless you +// are sure it does as you may otherwise overflow the kernel stack. + +// giving this fn a return value would help GCC, allegedly + +static void rx_schedule (hrz_dev * dev, int irq) { + unsigned int rx_bytes; + + int pio_instead = 0; +#ifndef TAILRECURSIONWORKS + pio_instead = 1; + while (pio_instead) { +#endif + // bytes waiting for RX transfer + rx_bytes = dev->rx_bytes; + +#if 0 + spin_count = 0; + while (rd_regl (dev, MASTER_RX_COUNT_REG_OFF)) { + PRINTD (DBG_RX|DBG_WARN, "RX error: other PCI Bus Master RX still in progress!"); + if (++spin_count > 10) { + PRINTD (DBG_RX|DBG_ERR, "spun out waiting PCI Bus Master RX completion"); + wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0); + clear_bit (rx_busy, &dev->flags); + hrz_kfree_skb (dev->rx_skb); + return; + } + } +#endif + + // this code follows the TX code but (at the moment) there is only + // one region - the skb itself. I don't know if this will change, + // but it doesn't hurt to have the code here, disabled. + + if (rx_bytes) { + // start next transfer within same region + if (rx_bytes <= MAX_PIO_COUNT) { + PRINTD (DBG_RX|DBG_BUS, "(pio)"); + pio_instead = 1; + } + if (rx_bytes <= MAX_TRANSFER_COUNT) { + PRINTD (DBG_RX|DBG_BUS, "(simple or last multi)"); + dev->rx_bytes = 0; + } else { + PRINTD (DBG_RX|DBG_BUS, "(continuing multi)"); + dev->rx_bytes = rx_bytes - MAX_TRANSFER_COUNT; + rx_bytes = MAX_TRANSFER_COUNT; + } + } else { + // rx_bytes == 0 -- we're between regions + // regions remaining to transfer +#if 0 + unsigned int rx_regions = dev->rx_regions; +#else + unsigned int rx_regions = 0; +#endif + + if (rx_regions) { +#if 0 + // start a new region + dev->rx_addr = dev->rx_iovec->iov_base; + rx_bytes = dev->rx_iovec->iov_len; + ++dev->rx_iovec; + dev->rx_regions = rx_regions - 1; + + if (rx_bytes <= MAX_PIO_COUNT) { + PRINTD (DBG_RX|DBG_BUS, "(pio)"); + pio_instead = 1; + } + if (rx_bytes <= MAX_TRANSFER_COUNT) { + PRINTD (DBG_RX|DBG_BUS, "(full region)"); + dev->rx_bytes = 0; + } else { + PRINTD (DBG_RX|DBG_BUS, "(start multi region)"); + dev->rx_bytes = rx_bytes - MAX_TRANSFER_COUNT; + rx_bytes = MAX_TRANSFER_COUNT; + } +#endif + } else { + // rx_regions == 0 + // that's all folks - end of frame + struct sk_buff * skb = dev->rx_skb; + // dev->rx_iovec = 0; + + FLUSH_RX_CHANNEL (dev, dev->rx_channel); + + dump_skb ("<<<", dev->rx_channel, skb); + + PRINTD (DBG_RX|DBG_SKB, "push %p %u", skb->data, skb->len); + + { + struct atm_vcc * vcc = ATM_SKB(skb)->vcc; + // VC layer stats + atomic_inc(&vcc->stats->rx); + __net_timestamp(skb); + // end of our responsibility + vcc->push (vcc, skb); + } + } + } + + // note: writing RX_COUNT clears any interrupt condition + if (rx_bytes) { + if (pio_instead) { + if (irq) + wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0); + rds_regb (dev, DATA_PORT_OFF, dev->rx_addr, rx_bytes); + } else { + wr_regl (dev, MASTER_RX_ADDR_REG_OFF, virt_to_bus (dev->rx_addr)); + wr_regl (dev, MASTER_RX_COUNT_REG_OFF, rx_bytes); + } + dev->rx_addr += rx_bytes; + } else { + if (irq) + wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0); + // allow another RX thread to start + YELLOW_LED_ON(dev); + clear_bit (rx_busy, &dev->flags); + PRINTD (DBG_RX, "cleared rx_busy for dev %p", dev); + } + +#ifdef TAILRECURSIONWORKS + // and we all bless optimised tail calls + if (pio_instead) + return rx_schedule (dev, 0); + return; +#else + // grrrrrrr! + irq = 0; + } + return; +#endif +} + +/********** handle RX bus master complete events **********/ + +static void rx_bus_master_complete_handler (hrz_dev * dev) { + if (test_bit (rx_busy, &dev->flags)) { + rx_schedule (dev, 1); + } else { + PRINTD (DBG_RX|DBG_ERR, "unexpected RX bus master completion"); + // clear interrupt condition on adapter + wr_regl (dev, MASTER_RX_COUNT_REG_OFF, 0); + } + return; +} + +/********** (queue to) become the next TX thread **********/ + +static int tx_hold (hrz_dev * dev) { + PRINTD (DBG_TX, "sleeping at tx lock %p %lu", dev, dev->flags); + wait_event_interruptible(dev->tx_queue, (!test_and_set_bit(tx_busy, &dev->flags))); + PRINTD (DBG_TX, "woken at tx lock %p %lu", dev, dev->flags); + if (signal_pending (current)) + return -1; + PRINTD (DBG_TX, "set tx_busy for dev %p", dev); + return 0; +} + +/********** allow another TX thread to start **********/ + +static inline void tx_release (hrz_dev * dev) { + clear_bit (tx_busy, &dev->flags); + PRINTD (DBG_TX, "cleared tx_busy for dev %p", dev); + wake_up_interruptible (&dev->tx_queue); +} + +/********** schedule TX transfers **********/ + +static void tx_schedule (hrz_dev * const dev, int irq) { + unsigned int tx_bytes; + + int append_desc = 0; + + int pio_instead = 0; +#ifndef TAILRECURSIONWORKS + pio_instead = 1; + while (pio_instead) { +#endif + // bytes in current region waiting for TX transfer + tx_bytes = dev->tx_bytes; + +#if 0 + spin_count = 0; + while (rd_regl (dev, MASTER_TX_COUNT_REG_OFF)) { + PRINTD (DBG_TX|DBG_WARN, "TX error: other PCI Bus Master TX still in progress!"); + if (++spin_count > 10) { + PRINTD (DBG_TX|DBG_ERR, "spun out waiting PCI Bus Master TX completion"); + wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0); + tx_release (dev); + hrz_kfree_skb (dev->tx_skb); + return; + } + } +#endif + + if (tx_bytes) { + // start next transfer within same region + if (!test_bit (ultra, &dev->flags) || tx_bytes <= MAX_PIO_COUNT) { + PRINTD (DBG_TX|DBG_BUS, "(pio)"); + pio_instead = 1; + } + if (tx_bytes <= MAX_TRANSFER_COUNT) { + PRINTD (DBG_TX|DBG_BUS, "(simple or last multi)"); + if (!dev->tx_iovec) { + // end of last region + append_desc = 1; + } + dev->tx_bytes = 0; + } else { + PRINTD (DBG_TX|DBG_BUS, "(continuing multi)"); + dev->tx_bytes = tx_bytes - MAX_TRANSFER_COUNT; + tx_bytes = MAX_TRANSFER_COUNT; + } + } else { + // tx_bytes == 0 -- we're between regions + // regions remaining to transfer + unsigned int tx_regions = dev->tx_regions; + + if (tx_regions) { + // start a new region + dev->tx_addr = dev->tx_iovec->iov_base; + tx_bytes = dev->tx_iovec->iov_len; + ++dev->tx_iovec; + dev->tx_regions = tx_regions - 1; + + if (!test_bit (ultra, &dev->flags) || tx_bytes <= MAX_PIO_COUNT) { + PRINTD (DBG_TX|DBG_BUS, "(pio)"); + pio_instead = 1; + } + if (tx_bytes <= MAX_TRANSFER_COUNT) { + PRINTD (DBG_TX|DBG_BUS, "(full region)"); + dev->tx_bytes = 0; + } else { + PRINTD (DBG_TX|DBG_BUS, "(start multi region)"); + dev->tx_bytes = tx_bytes - MAX_TRANSFER_COUNT; + tx_bytes = MAX_TRANSFER_COUNT; + } + } else { + // tx_regions == 0 + // that's all folks - end of frame + struct sk_buff * skb = dev->tx_skb; + dev->tx_iovec = NULL; + + // VC layer stats + atomic_inc(&ATM_SKB(skb)->vcc->stats->tx); + + // free the skb + hrz_kfree_skb (skb); + } + } + + // note: writing TX_COUNT clears any interrupt condition + if (tx_bytes) { + if (pio_instead) { + if (irq) + wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0); + wrs_regb (dev, DATA_PORT_OFF, dev->tx_addr, tx_bytes); + if (append_desc) + wr_regl (dev, TX_DESCRIPTOR_PORT_OFF, cpu_to_be32 (dev->tx_skb->len)); + } else { + wr_regl (dev, MASTER_TX_ADDR_REG_OFF, virt_to_bus (dev->tx_addr)); + if (append_desc) + wr_regl (dev, TX_DESCRIPTOR_REG_OFF, cpu_to_be32 (dev->tx_skb->len)); + wr_regl (dev, MASTER_TX_COUNT_REG_OFF, + append_desc + ? tx_bytes | MASTER_TX_AUTO_APPEND_DESC + : tx_bytes); + } + dev->tx_addr += tx_bytes; + } else { + if (irq) + wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0); + YELLOW_LED_ON(dev); + tx_release (dev); + } + +#ifdef TAILRECURSIONWORKS + // and we all bless optimised tail calls + if (pio_instead) + return tx_schedule (dev, 0); + return; +#else + // grrrrrrr! + irq = 0; + } + return; +#endif +} + +/********** handle TX bus master complete events **********/ + +static void tx_bus_master_complete_handler (hrz_dev * dev) { + if (test_bit (tx_busy, &dev->flags)) { + tx_schedule (dev, 1); + } else { + PRINTD (DBG_TX|DBG_ERR, "unexpected TX bus master completion"); + // clear interrupt condition on adapter + wr_regl (dev, MASTER_TX_COUNT_REG_OFF, 0); + } + return; +} + +/********** move RX Q pointer to next item in circular buffer **********/ + +// called only from IRQ sub-handler +static u32 rx_queue_entry_next (hrz_dev * dev) { + u32 rx_queue_entry; + spin_lock (&dev->mem_lock); + rx_queue_entry = rd_mem (dev, &dev->rx_q_entry->entry); + if (dev->rx_q_entry == dev->rx_q_wrap) + dev->rx_q_entry = dev->rx_q_reset; + else + dev->rx_q_entry++; + wr_regw (dev, RX_QUEUE_RD_PTR_OFF, dev->rx_q_entry - dev->rx_q_reset); + spin_unlock (&dev->mem_lock); + return rx_queue_entry; +} + +/********** handle RX data received by device **********/ + +// called from IRQ handler +static void rx_data_av_handler (hrz_dev * dev) { + u32 rx_queue_entry; + u32 rx_queue_entry_flags; + u16 rx_len; + u16 rx_channel; + + PRINTD (DBG_FLOW, "hrz_data_av_handler"); + + // try to grab rx lock (not possible during RX bus mastering) + if (test_and_set_bit (rx_busy, &dev->flags)) { + PRINTD (DBG_RX, "locked out of rx lock"); + return; + } + PRINTD (DBG_RX, "set rx_busy for dev %p", dev); + // lock is cleared if we fail now, o/w after bus master completion + + YELLOW_LED_OFF(dev); + + rx_queue_entry = rx_queue_entry_next (dev); + + rx_len = rx_q_entry_to_length (rx_queue_entry); + rx_channel = rx_q_entry_to_rx_channel (rx_queue_entry); + + WAIT_FLUSH_RX_COMPLETE (dev); + + SELECT_RX_CHANNEL (dev, rx_channel); + + PRINTD (DBG_RX, "rx_queue_entry is: %#x", rx_queue_entry); + rx_queue_entry_flags = rx_queue_entry & (RX_CRC_32_OK|RX_COMPLETE_FRAME|SIMONS_DODGEY_MARKER); + + if (!rx_len) { + // (at least) bus-mastering breaks if we try to handle a + // zero-length frame, besides AAL5 does not support them + PRINTK (KERN_ERR, "zero-length frame!"); + rx_queue_entry_flags &= ~RX_COMPLETE_FRAME; + } + + if (rx_queue_entry_flags & SIMONS_DODGEY_MARKER) { + PRINTD (DBG_RX|DBG_ERR, "Simon's marker detected!"); + } + if (rx_queue_entry_flags == (RX_CRC_32_OK | RX_COMPLETE_FRAME)) { + struct atm_vcc * atm_vcc; + + PRINTD (DBG_RX, "got a frame on rx_channel %x len %u", rx_channel, rx_len); + + atm_vcc = dev->rxer[rx_channel]; + // if no vcc is assigned to this channel, we should drop the frame + // (is this what SIMONS etc. was trying to achieve?) + + if (atm_vcc) { + + if (atm_vcc->qos.rxtp.traffic_class != ATM_NONE) { + + if (rx_len <= atm_vcc->qos.rxtp.max_sdu) { + + struct sk_buff * skb = atm_alloc_charge (atm_vcc, rx_len, GFP_ATOMIC); + if (skb) { + // remember this so we can push it later + dev->rx_skb = skb; + // remember this so we can flush it later + dev->rx_channel = rx_channel; + + // prepare socket buffer + skb_put (skb, rx_len); + ATM_SKB(skb)->vcc = atm_vcc; + + // simple transfer + // dev->rx_regions = 0; + // dev->rx_iovec = 0; + dev->rx_bytes = rx_len; + dev->rx_addr = skb->data; + PRINTD (DBG_RX, "RX start simple transfer (addr %p, len %d)", + skb->data, rx_len); + + // do the business + rx_schedule (dev, 0); + return; + + } else { + PRINTD (DBG_SKB|DBG_WARN, "failed to get skb"); + } + + } else { + PRINTK (KERN_INFO, "frame received on TX-only VC %x", rx_channel); + // do we count this? + } + + } else { + PRINTK (KERN_WARNING, "dropped over-size frame"); + // do we count this? + } + + } else { + PRINTD (DBG_WARN|DBG_VCC|DBG_RX, "no VCC for this frame (VC closed)"); + // do we count this? + } + + } else { + // Wait update complete ? SPONG + } + + // RX was aborted + YELLOW_LED_ON(dev); + + FLUSH_RX_CHANNEL (dev,rx_channel); + clear_bit (rx_busy, &dev->flags); + + return; +} + +/********** interrupt handler **********/ + +static irqreturn_t interrupt_handler(int irq, void *dev_id) +{ + hrz_dev *dev = dev_id; + u32 int_source; + unsigned int irq_ok; + + PRINTD (DBG_FLOW, "interrupt_handler: %p", dev_id); + + // definitely for us + irq_ok = 0; + while ((int_source = rd_regl (dev, INT_SOURCE_REG_OFF) + & INTERESTING_INTERRUPTS)) { + // In the interests of fairness, the handlers below are + // called in sequence and without immediate return to the head of + // the while loop. This is only of issue for slow hosts (or when + // debugging messages are on). Really slow hosts may find a fast + // sender keeps them permanently in the IRQ handler. :( + + // (only an issue for slow hosts) RX completion goes before + // rx_data_av as the former implies rx_busy and so the latter + // would just abort. If it reschedules another transfer + // (continuing the same frame) then it will not clear rx_busy. + + // (only an issue for slow hosts) TX completion goes before RX + // data available as it is a much shorter routine - there is the + // chance that any further transfers it schedules will be complete + // by the time of the return to the head of the while loop + + if (int_source & RX_BUS_MASTER_COMPLETE) { + ++irq_ok; + PRINTD (DBG_IRQ|DBG_BUS|DBG_RX, "rx_bus_master_complete asserted"); + rx_bus_master_complete_handler (dev); + } + if (int_source & TX_BUS_MASTER_COMPLETE) { + ++irq_ok; + PRINTD (DBG_IRQ|DBG_BUS|DBG_TX, "tx_bus_master_complete asserted"); + tx_bus_master_complete_handler (dev); + } + if (int_source & RX_DATA_AV) { + ++irq_ok; + PRINTD (DBG_IRQ|DBG_RX, "rx_data_av asserted"); + rx_data_av_handler (dev); + } + } + if (irq_ok) { + PRINTD (DBG_IRQ, "work done: %u", irq_ok); + } else { + PRINTD (DBG_IRQ|DBG_WARN, "spurious interrupt source: %#x", int_source); + } + + PRINTD (DBG_IRQ|DBG_FLOW, "interrupt_handler done: %p", dev_id); + if (irq_ok) + return IRQ_HANDLED; + return IRQ_NONE; +} + +/********** housekeeping **********/ + +static void do_housekeeping (struct timer_list *t) { + // just stats at the moment + hrz_dev * dev = from_timer(dev, t, housekeeping); + + // collect device-specific (not driver/atm-linux) stats here + dev->tx_cell_count += rd_regw (dev, TX_CELL_COUNT_OFF); + dev->rx_cell_count += rd_regw (dev, RX_CELL_COUNT_OFF); + dev->hec_error_count += rd_regw (dev, HEC_ERROR_COUNT_OFF); + dev->unassigned_cell_count += rd_regw (dev, UNASSIGNED_CELL_COUNT_OFF); + + mod_timer (&dev->housekeeping, jiffies + HZ/10); + + return; +} + +/********** find an idle channel for TX and set it up **********/ + +// called with tx_busy set +static short setup_idle_tx_channel (hrz_dev * dev, hrz_vcc * vcc) { + unsigned short idle_channels; + short tx_channel = -1; + unsigned int spin_count; + PRINTD (DBG_FLOW|DBG_TX, "setup_idle_tx_channel %p", dev); + + // better would be to fail immediately, the caller can then decide whether + // to wait or drop (depending on whether this is UBR etc.) + spin_count = 0; + while (!(idle_channels = rd_regw (dev, TX_STATUS_OFF) & IDLE_CHANNELS_MASK)) { + PRINTD (DBG_TX|DBG_WARN, "waiting for idle TX channel"); + // delay a bit here + if (++spin_count > 100) { + PRINTD (DBG_TX|DBG_ERR, "spun out waiting for idle TX channel"); + return -EBUSY; + } + } + + // got an idle channel + { + // tx_idle ensures we look for idle channels in RR order + int chan = dev->tx_idle; + + int keep_going = 1; + while (keep_going) { + if (idle_channels & (1<tx_idle = chan; + } + + // set up the channel we found + { + // Initialise the cell header in the transmit channel descriptor + // a.k.a. prepare the channel and remember that we have done so. + + tx_ch_desc * tx_desc = &memmap->tx_descs[tx_channel]; + u32 rd_ptr; + u32 wr_ptr; + u16 channel = vcc->channel; + + unsigned long flags; + spin_lock_irqsave (&dev->mem_lock, flags); + + // Update the transmit channel record. + dev->tx_channel_record[tx_channel] = channel; + + // xBR channel + update_tx_channel_config (dev, tx_channel, RATE_TYPE_ACCESS, + vcc->tx_xbr_bits); + + // Update the PCR counter preload value etc. + update_tx_channel_config (dev, tx_channel, PCR_TIMER_ACCESS, + vcc->tx_pcr_bits); + +#if 0 + if (vcc->tx_xbr_bits == VBR_RATE_TYPE) { + // SCR timer + update_tx_channel_config (dev, tx_channel, SCR_TIMER_ACCESS, + vcc->tx_scr_bits); + + // Bucket size... + update_tx_channel_config (dev, tx_channel, BUCKET_CAPACITY_ACCESS, + vcc->tx_bucket_bits); + + // ... and fullness + update_tx_channel_config (dev, tx_channel, BUCKET_FULLNESS_ACCESS, + vcc->tx_bucket_bits); + } +#endif + + // Initialise the read and write buffer pointers + rd_ptr = rd_mem (dev, &tx_desc->rd_buf_type) & BUFFER_PTR_MASK; + wr_ptr = rd_mem (dev, &tx_desc->wr_buf_type) & BUFFER_PTR_MASK; + + // idle TX channels should have identical pointers + if (rd_ptr != wr_ptr) { + PRINTD (DBG_TX|DBG_ERR, "TX buffer pointers are broken!"); + // spin_unlock... return -E... + // I wonder if gcc would get rid of one of the pointer aliases + } + PRINTD (DBG_TX, "TX buffer pointers are: rd %x, wr %x.", + rd_ptr, wr_ptr); + + switch (vcc->aal) { + case aal0: + PRINTD (DBG_QOS|DBG_TX, "tx_channel: aal0"); + rd_ptr |= CHANNEL_TYPE_RAW_CELLS; + wr_ptr |= CHANNEL_TYPE_RAW_CELLS; + break; + case aal34: + PRINTD (DBG_QOS|DBG_TX, "tx_channel: aal34"); + rd_ptr |= CHANNEL_TYPE_AAL3_4; + wr_ptr |= CHANNEL_TYPE_AAL3_4; + break; + case aal5: + rd_ptr |= CHANNEL_TYPE_AAL5; + wr_ptr |= CHANNEL_TYPE_AAL5; + // Initialise the CRC + wr_mem (dev, &tx_desc->partial_crc, INITIAL_CRC); + break; + } + + wr_mem (dev, &tx_desc->rd_buf_type, rd_ptr); + wr_mem (dev, &tx_desc->wr_buf_type, wr_ptr); + + // Write the Cell Header + // Payload Type, CLP and GFC would go here if non-zero + wr_mem (dev, &tx_desc->cell_header, channel); + + spin_unlock_irqrestore (&dev->mem_lock, flags); + } + + return tx_channel; +} + +/********** send a frame **********/ + +static int hrz_send (struct atm_vcc * atm_vcc, struct sk_buff * skb) { + unsigned int spin_count; + int free_buffers; + hrz_dev * dev = HRZ_DEV(atm_vcc->dev); + hrz_vcc * vcc = HRZ_VCC(atm_vcc); + u16 channel = vcc->channel; + + u32 buffers_required; + + /* signed for error return */ + short tx_channel; + + PRINTD (DBG_FLOW|DBG_TX, "hrz_send vc %x data %p len %u", + channel, skb->data, skb->len); + + dump_skb (">>>", channel, skb); + + if (atm_vcc->qos.txtp.traffic_class == ATM_NONE) { + PRINTK (KERN_ERR, "attempt to send on RX-only VC %x", channel); + hrz_kfree_skb (skb); + return -EIO; + } + + // don't understand this + ATM_SKB(skb)->vcc = atm_vcc; + + if (skb->len > atm_vcc->qos.txtp.max_sdu) { + PRINTK (KERN_ERR, "sk_buff length greater than agreed max_sdu, dropping..."); + hrz_kfree_skb (skb); + return -EIO; + } + + if (!channel) { + PRINTD (DBG_ERR|DBG_TX, "attempt to transmit on zero (rx_)channel"); + hrz_kfree_skb (skb); + return -EIO; + } + +#if 0 + { + // where would be a better place for this? housekeeping? + u16 status; + pci_read_config_word (dev->pci_dev, PCI_STATUS, &status); + if (status & PCI_STATUS_REC_MASTER_ABORT) { + PRINTD (DBG_BUS|DBG_ERR, "Clearing PCI Master Abort (and cleaning up)"); + status &= ~PCI_STATUS_REC_MASTER_ABORT; + pci_write_config_word (dev->pci_dev, PCI_STATUS, status); + if (test_bit (tx_busy, &dev->flags)) { + hrz_kfree_skb (dev->tx_skb); + tx_release (dev); + } + } + } +#endif + +#ifdef DEBUG_HORIZON + /* wey-hey! */ + if (channel == 1023) { + unsigned int i; + unsigned short d = 0; + char * s = skb->data; + if (*s++ == 'D') { + for (i = 0; i < 4; ++i) + d = (d << 4) | hex_to_bin(*s++); + PRINTK (KERN_INFO, "debug bitmap is now %hx", debug = d); + } + } +#endif + + // wait until TX is free and grab lock + if (tx_hold (dev)) { + hrz_kfree_skb (skb); + return -ERESTARTSYS; + } + + // Wait for enough space to be available in transmit buffer memory. + + // should be number of cells needed + 2 (according to hardware docs) + // = ((framelen+8)+47) / 48 + 2 + // = (framelen+7) / 48 + 3, hmm... faster to put addition inside XXX + buffers_required = (skb->len+(ATM_AAL5_TRAILER-1)) / ATM_CELL_PAYLOAD + 3; + + // replace with timer and sleep, add dev->tx_buffers_queue (max 1 entry) + spin_count = 0; + while ((free_buffers = rd_regw (dev, TX_FREE_BUFFER_COUNT_OFF)) < buffers_required) { + PRINTD (DBG_TX, "waiting for free TX buffers, got %d of %d", + free_buffers, buffers_required); + // what is the appropriate delay? implement a timeout? (depending on line speed?) + // mdelay (1); + // what happens if we kill (current_pid, SIGKILL) ? + schedule(); + if (++spin_count > 1000) { + PRINTD (DBG_TX|DBG_ERR, "spun out waiting for tx buffers, got %d of %d", + free_buffers, buffers_required); + tx_release (dev); + hrz_kfree_skb (skb); + return -ERESTARTSYS; + } + } + + // Select a channel to transmit the frame on. + if (channel == dev->last_vc) { + PRINTD (DBG_TX, "last vc hack: hit"); + tx_channel = dev->tx_last; + } else { + PRINTD (DBG_TX, "last vc hack: miss"); + // Are we currently transmitting this VC on one of the channels? + for (tx_channel = 0; tx_channel < TX_CHANS; ++tx_channel) + if (dev->tx_channel_record[tx_channel] == channel) { + PRINTD (DBG_TX, "vc already on channel: hit"); + break; + } + if (tx_channel == TX_CHANS) { + PRINTD (DBG_TX, "vc already on channel: miss"); + // Find and set up an idle channel. + tx_channel = setup_idle_tx_channel (dev, vcc); + if (tx_channel < 0) { + PRINTD (DBG_TX|DBG_ERR, "failed to get channel"); + tx_release (dev); + return tx_channel; + } + } + + PRINTD (DBG_TX, "got channel"); + SELECT_TX_CHANNEL(dev, tx_channel); + + dev->last_vc = channel; + dev->tx_last = tx_channel; + } + + PRINTD (DBG_TX, "using channel %u", tx_channel); + + YELLOW_LED_OFF(dev); + + // TX start transfer + + { + unsigned int tx_len = skb->len; + unsigned int tx_iovcnt = skb_shinfo(skb)->nr_frags; + // remember this so we can free it later + dev->tx_skb = skb; + + if (tx_iovcnt) { + // scatter gather transfer + dev->tx_regions = tx_iovcnt; + dev->tx_iovec = NULL; /* @@@ needs rewritten */ + dev->tx_bytes = 0; + PRINTD (DBG_TX|DBG_BUS, "TX start scatter-gather transfer (iovec %p, len %d)", + skb->data, tx_len); + tx_release (dev); + hrz_kfree_skb (skb); + return -EIO; + } else { + // simple transfer + dev->tx_regions = 0; + dev->tx_iovec = NULL; + dev->tx_bytes = tx_len; + dev->tx_addr = skb->data; + PRINTD (DBG_TX|DBG_BUS, "TX start simple transfer (addr %p, len %d)", + skb->data, tx_len); + } + + // and do the business + tx_schedule (dev, 0); + + } + + return 0; +} + +/********** reset a card **********/ + +static void hrz_reset (const hrz_dev * dev) { + u32 control_0_reg = rd_regl (dev, CONTROL_0_REG); + + // why not set RESET_HORIZON to one and wait for the card to + // reassert that bit as zero? Like so: + control_0_reg = control_0_reg & RESET_HORIZON; + wr_regl (dev, CONTROL_0_REG, control_0_reg); + while (control_0_reg & RESET_HORIZON) + control_0_reg = rd_regl (dev, CONTROL_0_REG); + + // old reset code retained: + wr_regl (dev, CONTROL_0_REG, control_0_reg | + RESET_ATM | RESET_RX | RESET_TX | RESET_HOST); + // just guessing here + udelay (1000); + + wr_regl (dev, CONTROL_0_REG, control_0_reg); +} + +/********** read the burnt in address **********/ + +static void WRITE_IT_WAIT (const hrz_dev *dev, u32 ctrl) +{ + wr_regl (dev, CONTROL_0_REG, ctrl); + udelay (5); +} + +static void CLOCK_IT (const hrz_dev *dev, u32 ctrl) +{ + // DI must be valid around rising SK edge + WRITE_IT_WAIT(dev, ctrl & ~SEEPROM_SK); + WRITE_IT_WAIT(dev, ctrl | SEEPROM_SK); +} + +static u16 read_bia(const hrz_dev *dev, u16 addr) +{ + u32 ctrl = rd_regl (dev, CONTROL_0_REG); + + const unsigned int addr_bits = 6; + const unsigned int data_bits = 16; + + unsigned int i; + + u16 res; + + ctrl &= ~(SEEPROM_CS | SEEPROM_SK | SEEPROM_DI); + WRITE_IT_WAIT(dev, ctrl); + + // wake Serial EEPROM and send 110 (READ) command + ctrl |= (SEEPROM_CS | SEEPROM_DI); + CLOCK_IT(dev, ctrl); + + ctrl |= SEEPROM_DI; + CLOCK_IT(dev, ctrl); + + ctrl &= ~SEEPROM_DI; + CLOCK_IT(dev, ctrl); + + for (i=0; i> 1; + + CLOCK_IT(dev, ctrl); + + if (rd_regl (dev, CONTROL_0_REG) & SEEPROM_DO) + res |= (1 << (data_bits-1)); + } + + ctrl &= ~(SEEPROM_SK | SEEPROM_CS); + WRITE_IT_WAIT(dev, ctrl); + + return res; +} + +/********** initialise a card **********/ + +static int hrz_init(hrz_dev *dev) +{ + int onefivefive; + + u16 chan; + + int buff_count; + + HDW * mem; + + cell_buf * tx_desc; + cell_buf * rx_desc; + + u32 ctrl; + + ctrl = rd_regl (dev, CONTROL_0_REG); + PRINTD (DBG_INFO, "ctrl0reg is %#x", ctrl); + onefivefive = ctrl & ATM_LAYER_STATUS; + + if (onefivefive) + printk (DEV_LABEL ": Horizon Ultra (at 155.52 MBps)"); + else + printk (DEV_LABEL ": Horizon (at 25 MBps)"); + + printk (":"); + // Reset the card to get everything in a known state + + printk (" reset"); + hrz_reset (dev); + + // Clear all the buffer memory + + printk (" clearing memory"); + + for (mem = (HDW *) memmap; mem < (HDW *) (memmap + 1); ++mem) + wr_mem (dev, mem, 0); + + printk (" tx channels"); + + // All transmit eight channels are set up as AAL5 ABR channels with + // a 16us cell spacing. Why? + + // Channel 0 gets the free buffer at 100h, channel 1 gets the free + // buffer at 110h etc. + + for (chan = 0; chan < TX_CHANS; ++chan) { + tx_ch_desc * tx_desc = &memmap->tx_descs[chan]; + cell_buf * buf = &memmap->inittxbufs[chan]; + + // initialise the read and write buffer pointers + wr_mem (dev, &tx_desc->rd_buf_type, BUF_PTR(buf)); + wr_mem (dev, &tx_desc->wr_buf_type, BUF_PTR(buf)); + + // set the status of the initial buffers to empty + wr_mem (dev, &buf->next, BUFF_STATUS_EMPTY); + } + + // Use space bufn3 at the moment for tx buffers + + printk (" tx buffers"); + + tx_desc = memmap->bufn3; + + wr_mem (dev, &memmap->txfreebufstart.next, BUF_PTR(tx_desc) | BUFF_STATUS_EMPTY); + + for (buff_count = 0; buff_count < BUFN3_SIZE-1; buff_count++) { + wr_mem (dev, &tx_desc->next, BUF_PTR(tx_desc+1) | BUFF_STATUS_EMPTY); + tx_desc++; + } + + wr_mem (dev, &tx_desc->next, BUF_PTR(&memmap->txfreebufend) | BUFF_STATUS_EMPTY); + + // Initialise the transmit free buffer count + wr_regw (dev, TX_FREE_BUFFER_COUNT_OFF, BUFN3_SIZE); + + printk (" rx channels"); + + // Initialise all of the receive channels to be AAL5 disabled with + // an interrupt threshold of 0 + + for (chan = 0; chan < RX_CHANS; ++chan) { + rx_ch_desc * rx_desc = &memmap->rx_descs[chan]; + + wr_mem (dev, &rx_desc->wr_buf_type, CHANNEL_TYPE_AAL5 | RX_CHANNEL_DISABLED); + } + + printk (" rx buffers"); + + // Use space bufn4 at the moment for rx buffers + + rx_desc = memmap->bufn4; + + wr_mem (dev, &memmap->rxfreebufstart.next, BUF_PTR(rx_desc) | BUFF_STATUS_EMPTY); + + for (buff_count = 0; buff_count < BUFN4_SIZE-1; buff_count++) { + wr_mem (dev, &rx_desc->next, BUF_PTR(rx_desc+1) | BUFF_STATUS_EMPTY); + + rx_desc++; + } + + wr_mem (dev, &rx_desc->next, BUF_PTR(&memmap->rxfreebufend) | BUFF_STATUS_EMPTY); + + // Initialise the receive free buffer count + wr_regw (dev, RX_FREE_BUFFER_COUNT_OFF, BUFN4_SIZE); + + // Initialize Horizons registers + + // TX config + wr_regw (dev, TX_CONFIG_OFF, + ABR_ROUND_ROBIN | TX_NORMAL_OPERATION | DRVR_DRVRBAR_ENABLE); + + // RX config. Use 10-x VC bits, x VP bits, non user cells in channel 0. + wr_regw (dev, RX_CONFIG_OFF, + DISCARD_UNUSED_VPI_VCI_BITS_SET | NON_USER_CELLS_IN_ONE_CHANNEL | vpi_bits); + + // RX line config + wr_regw (dev, RX_LINE_CONFIG_OFF, + LOCK_DETECT_ENABLE | FREQUENCY_DETECT_ENABLE | GXTALOUT_SELECT_DIV4); + + // Set the max AAL5 cell count to be just enough to contain the + // largest AAL5 frame that the user wants to receive + wr_regw (dev, MAX_AAL5_CELL_COUNT_OFF, + DIV_ROUND_UP(max_rx_size + ATM_AAL5_TRAILER, ATM_CELL_PAYLOAD)); + + // Enable receive + wr_regw (dev, RX_CONFIG_OFF, rd_regw (dev, RX_CONFIG_OFF) | RX_ENABLE); + + printk (" control"); + + // Drive the OE of the LEDs then turn the green LED on + ctrl |= GREEN_LED_OE | YELLOW_LED_OE | GREEN_LED | YELLOW_LED; + wr_regl (dev, CONTROL_0_REG, ctrl); + + // Test for a 155-capable card + + if (onefivefive) { + // Select 155 mode... make this a choice (or: how do we detect + // external line speed and switch?) + ctrl |= ATM_LAYER_SELECT; + wr_regl (dev, CONTROL_0_REG, ctrl); + + // test SUNI-lite vs SAMBA + + // Register 0x00 in the SUNI will have some of bits 3-7 set, and + // they will always be zero for the SAMBA. Ha! Bloody hardware + // engineers. It'll never work. + + if (rd_framer (dev, 0) & 0x00f0) { + // SUNI + printk (" SUNI"); + + // Reset, just in case + wr_framer (dev, 0x00, 0x0080); + wr_framer (dev, 0x00, 0x0000); + + // Configure transmit FIFO + wr_framer (dev, 0x63, rd_framer (dev, 0x63) | 0x0002); + + // Set line timed mode + wr_framer (dev, 0x05, rd_framer (dev, 0x05) | 0x0001); + } else { + // SAMBA + printk (" SAMBA"); + + // Reset, just in case + wr_framer (dev, 0, rd_framer (dev, 0) | 0x0001); + wr_framer (dev, 0, rd_framer (dev, 0) &~ 0x0001); + + // Turn off diagnostic loopback and enable line-timed mode + wr_framer (dev, 0, 0x0002); + + // Turn on transmit outputs + wr_framer (dev, 2, 0x0B80); + } + } else { + // Select 25 mode + ctrl &= ~ATM_LAYER_SELECT; + + // Madge B154 setup + // none required? + } + + printk (" LEDs"); + + GREEN_LED_ON(dev); + YELLOW_LED_ON(dev); + + printk (" ESI="); + + { + u16 b = 0; + int i; + u8 * esi = dev->atm_dev->esi; + + // in the card I have, EEPROM + // addresses 0, 1, 2 contain 0 + // addresess 5, 6 etc. contain ffff + // NB: Madge prefix is 00 00 f6 (which is 00 00 6f in Ethernet bit order) + // the read_bia routine gets the BIA in Ethernet bit order + + for (i=0; i < ESI_LEN; ++i) { + if (i % 2 == 0) + b = read_bia (dev, i/2 + 2); + else + b = b >> 8; + esi[i] = b & 0xFF; + printk ("%02x", esi[i]); + } + } + + // Enable RX_Q and ?X_COMPLETE interrupts only + wr_regl (dev, INT_ENABLE_REG_OFF, INTERESTING_INTERRUPTS); + printk (" IRQ on"); + + printk (".\n"); + + return onefivefive; +} + +/********** check max_sdu **********/ + +static int check_max_sdu (hrz_aal aal, struct atm_trafprm * tp, unsigned int max_frame_size) { + PRINTD (DBG_FLOW|DBG_QOS, "check_max_sdu"); + + switch (aal) { + case aal0: + if (!(tp->max_sdu)) { + PRINTD (DBG_QOS, "defaulting max_sdu"); + tp->max_sdu = ATM_AAL0_SDU; + } else if (tp->max_sdu != ATM_AAL0_SDU) { + PRINTD (DBG_QOS|DBG_ERR, "rejecting max_sdu"); + return -EINVAL; + } + break; + case aal34: + if (tp->max_sdu == 0 || tp->max_sdu > ATM_MAX_AAL34_PDU) { + PRINTD (DBG_QOS, "%sing max_sdu", tp->max_sdu ? "capp" : "default"); + tp->max_sdu = ATM_MAX_AAL34_PDU; + } + break; + case aal5: + if (tp->max_sdu == 0 || tp->max_sdu > max_frame_size) { + PRINTD (DBG_QOS, "%sing max_sdu", tp->max_sdu ? "capp" : "default"); + tp->max_sdu = max_frame_size; + } + break; + } + return 0; +} + +/********** check pcr **********/ + +// something like this should be part of ATM Linux +static int atm_pcr_check (struct atm_trafprm * tp, unsigned int pcr) { + // we are assuming non-UBR, and non-special values of pcr + if (tp->min_pcr == ATM_MAX_PCR) + PRINTD (DBG_QOS, "luser gave min_pcr = ATM_MAX_PCR"); + else if (tp->min_pcr < 0) + PRINTD (DBG_QOS, "luser gave negative min_pcr"); + else if (tp->min_pcr && tp->min_pcr > pcr) + PRINTD (DBG_QOS, "pcr less than min_pcr"); + else + // !! max_pcr = UNSPEC (0) is equivalent to max_pcr = MAX (-1) + // easier to #define ATM_MAX_PCR 0 and have all rates unsigned? + // [this would get rid of next two conditionals] + if ((0) && tp->max_pcr == ATM_MAX_PCR) + PRINTD (DBG_QOS, "luser gave max_pcr = ATM_MAX_PCR"); + else if ((tp->max_pcr != ATM_MAX_PCR) && tp->max_pcr < 0) + PRINTD (DBG_QOS, "luser gave negative max_pcr"); + else if (tp->max_pcr && tp->max_pcr != ATM_MAX_PCR && tp->max_pcr < pcr) + PRINTD (DBG_QOS, "pcr greater than max_pcr"); + else { + // each limit unspecified or not violated + PRINTD (DBG_QOS, "xBR(pcr) OK"); + return 0; + } + PRINTD (DBG_QOS, "pcr=%u, tp: min_pcr=%d, pcr=%d, max_pcr=%d", + pcr, tp->min_pcr, tp->pcr, tp->max_pcr); + return -EINVAL; +} + +/********** open VC **********/ + +static int hrz_open (struct atm_vcc *atm_vcc) +{ + int error; + u16 channel; + + struct atm_qos * qos; + struct atm_trafprm * txtp; + struct atm_trafprm * rxtp; + + hrz_dev * dev = HRZ_DEV(atm_vcc->dev); + hrz_vcc vcc; + hrz_vcc * vccp; // allocated late + short vpi = atm_vcc->vpi; + int vci = atm_vcc->vci; + PRINTD (DBG_FLOW|DBG_VCC, "hrz_open %x %x", vpi, vci); + +#ifdef ATM_VPI_UNSPEC + // UNSPEC is deprecated, remove this code eventually + if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) { + PRINTK (KERN_WARNING, "rejecting open with unspecified VPI/VCI (deprecated)"); + return -EINVAL; + } +#endif + + error = vpivci_to_channel (&channel, vpi, vci); + if (error) { + PRINTD (DBG_WARN|DBG_VCC, "VPI/VCI out of range: %hd/%d", vpi, vci); + return error; + } + + vcc.channel = channel; + // max speed for the moment + vcc.tx_rate = 0x0; + + qos = &atm_vcc->qos; + + // check AAL and remember it + switch (qos->aal) { + case ATM_AAL0: + // we would if it were 48 bytes and not 52! + PRINTD (DBG_QOS|DBG_VCC, "AAL0"); + vcc.aal = aal0; + break; + case ATM_AAL34: + // we would if I knew how do the SAR! + PRINTD (DBG_QOS|DBG_VCC, "AAL3/4"); + vcc.aal = aal34; + break; + case ATM_AAL5: + PRINTD (DBG_QOS|DBG_VCC, "AAL5"); + vcc.aal = aal5; + break; + default: + PRINTD (DBG_QOS|DBG_VCC, "Bad AAL!"); + return -EINVAL; + } + + // TX traffic parameters + + // there are two, interrelated problems here: 1. the reservation of + // PCR is not a binary choice, we are given bounds and/or a + // desirable value; 2. the device is only capable of certain values, + // most of which are not integers. It is almost certainly acceptable + // to be off by a maximum of 1 to 10 cps. + + // Pragmatic choice: always store an integral PCR as that which has + // been allocated, even if we allocate a little (or a lot) less, + // after rounding. The actual allocation depends on what we can + // manage with our rate selection algorithm. The rate selection + // algorithm is given an integral PCR and a tolerance and told + // whether it should round the value up or down if the tolerance is + // exceeded; it returns: a) the actual rate selected (rounded up to + // the nearest integer), b) a bit pattern to feed to the timer + // register, and c) a failure value if no applicable rate exists. + + // Part of the job is done by atm_pcr_goal which gives us a PCR + // specification which says: EITHER grab the maximum available PCR + // (and perhaps a lower bound which we musn't pass), OR grab this + // amount, rounding down if you have to (and perhaps a lower bound + // which we musn't pass) OR grab this amount, rounding up if you + // have to (and perhaps an upper bound which we musn't pass). If any + // bounds ARE passed we fail. Note that rounding is only rounding to + // match device limitations, we do not round down to satisfy + // bandwidth availability even if this would not violate any given + // lower bound. + + // Note: telephony = 64kb/s = 48 byte cell payload @ 500/3 cells/s + // (say) so this is not even a binary fixpoint cell rate (but this + // device can do it). To avoid this sort of hassle we use a + // tolerance parameter (currently fixed at 10 cps). + + PRINTD (DBG_QOS, "TX:"); + + txtp = &qos->txtp; + + // set up defaults for no traffic + vcc.tx_rate = 0; + // who knows what would actually happen if you try and send on this? + vcc.tx_xbr_bits = IDLE_RATE_TYPE; + vcc.tx_pcr_bits = CLOCK_DISABLE; +#if 0 + vcc.tx_scr_bits = CLOCK_DISABLE; + vcc.tx_bucket_bits = 0; +#endif + + if (txtp->traffic_class != ATM_NONE) { + error = check_max_sdu (vcc.aal, txtp, max_tx_size); + if (error) { + PRINTD (DBG_QOS, "TX max_sdu check failed"); + return error; + } + + switch (txtp->traffic_class) { + case ATM_UBR: { + // we take "the PCR" as a rate-cap + // not reserved + vcc.tx_rate = 0; + make_rate (dev, 1<<30, round_nearest, &vcc.tx_pcr_bits, NULL); + vcc.tx_xbr_bits = ABR_RATE_TYPE; + break; + } +#if 0 + case ATM_ABR: { + // reserve min, allow up to max + vcc.tx_rate = 0; // ? + make_rate (dev, 1<<30, round_nearest, &vcc.tx_pcr_bits, 0); + vcc.tx_xbr_bits = ABR_RATE_TYPE; + break; + } +#endif + case ATM_CBR: { + int pcr = atm_pcr_goal (txtp); + rounding r; + if (!pcr) { + // down vs. up, remaining bandwidth vs. unlimited bandwidth!! + // should really have: once someone gets unlimited bandwidth + // that no more non-UBR channels can be opened until the + // unlimited one closes?? For the moment, round_down means + // greedy people actually get something and not nothing + r = round_down; + // slight race (no locking) here so we may get -EAGAIN + // later; the greedy bastards would deserve it :) + PRINTD (DBG_QOS, "snatching all remaining TX bandwidth"); + pcr = dev->tx_avail; + } else if (pcr < 0) { + r = round_down; + pcr = -pcr; + } else { + r = round_up; + } + error = make_rate_with_tolerance (dev, pcr, r, 10, + &vcc.tx_pcr_bits, &vcc.tx_rate); + if (error) { + PRINTD (DBG_QOS, "could not make rate from TX PCR"); + return error; + } + // not really clear what further checking is needed + error = atm_pcr_check (txtp, vcc.tx_rate); + if (error) { + PRINTD (DBG_QOS, "TX PCR failed consistency check"); + return error; + } + vcc.tx_xbr_bits = CBR_RATE_TYPE; + break; + } +#if 0 + case ATM_VBR: { + int pcr = atm_pcr_goal (txtp); + // int scr = atm_scr_goal (txtp); + int scr = pcr/2; // just for fun + unsigned int mbs = 60; // just for fun + rounding pr; + rounding sr; + unsigned int bucket; + if (!pcr) { + pr = round_nearest; + pcr = 1<<30; + } else if (pcr < 0) { + pr = round_down; + pcr = -pcr; + } else { + pr = round_up; + } + error = make_rate_with_tolerance (dev, pcr, pr, 10, + &vcc.tx_pcr_bits, 0); + if (!scr) { + // see comments for PCR with CBR above + sr = round_down; + // slight race (no locking) here so we may get -EAGAIN + // later; the greedy bastards would deserve it :) + PRINTD (DBG_QOS, "snatching all remaining TX bandwidth"); + scr = dev->tx_avail; + } else if (scr < 0) { + sr = round_down; + scr = -scr; + } else { + sr = round_up; + } + error = make_rate_with_tolerance (dev, scr, sr, 10, + &vcc.tx_scr_bits, &vcc.tx_rate); + if (error) { + PRINTD (DBG_QOS, "could not make rate from TX SCR"); + return error; + } + // not really clear what further checking is needed + // error = atm_scr_check (txtp, vcc.tx_rate); + if (error) { + PRINTD (DBG_QOS, "TX SCR failed consistency check"); + return error; + } + // bucket calculations (from a piece of paper...) cell bucket + // capacity must be largest integer smaller than m(p-s)/p + 1 + // where m = max burst size, p = pcr, s = scr + bucket = mbs*(pcr-scr)/pcr; + if (bucket*pcr != mbs*(pcr-scr)) + bucket += 1; + if (bucket > BUCKET_MAX_SIZE) { + PRINTD (DBG_QOS, "shrinking bucket from %u to %u", + bucket, BUCKET_MAX_SIZE); + bucket = BUCKET_MAX_SIZE; + } + vcc.tx_xbr_bits = VBR_RATE_TYPE; + vcc.tx_bucket_bits = bucket; + break; + } +#endif + default: { + PRINTD (DBG_QOS, "unsupported TX traffic class"); + return -EINVAL; + } + } + } + + // RX traffic parameters + + PRINTD (DBG_QOS, "RX:"); + + rxtp = &qos->rxtp; + + // set up defaults for no traffic + vcc.rx_rate = 0; + + if (rxtp->traffic_class != ATM_NONE) { + error = check_max_sdu (vcc.aal, rxtp, max_rx_size); + if (error) { + PRINTD (DBG_QOS, "RX max_sdu check failed"); + return error; + } + switch (rxtp->traffic_class) { + case ATM_UBR: { + // not reserved + break; + } +#if 0 + case ATM_ABR: { + // reserve min + vcc.rx_rate = 0; // ? + break; + } +#endif + case ATM_CBR: { + int pcr = atm_pcr_goal (rxtp); + if (!pcr) { + // slight race (no locking) here so we may get -EAGAIN + // later; the greedy bastards would deserve it :) + PRINTD (DBG_QOS, "snatching all remaining RX bandwidth"); + pcr = dev->rx_avail; + } else if (pcr < 0) { + pcr = -pcr; + } + vcc.rx_rate = pcr; + // not really clear what further checking is needed + error = atm_pcr_check (rxtp, vcc.rx_rate); + if (error) { + PRINTD (DBG_QOS, "RX PCR failed consistency check"); + return error; + } + break; + } +#if 0 + case ATM_VBR: { + // int scr = atm_scr_goal (rxtp); + int scr = 1<<16; // just for fun + if (!scr) { + // slight race (no locking) here so we may get -EAGAIN + // later; the greedy bastards would deserve it :) + PRINTD (DBG_QOS, "snatching all remaining RX bandwidth"); + scr = dev->rx_avail; + } else if (scr < 0) { + scr = -scr; + } + vcc.rx_rate = scr; + // not really clear what further checking is needed + // error = atm_scr_check (rxtp, vcc.rx_rate); + if (error) { + PRINTD (DBG_QOS, "RX SCR failed consistency check"); + return error; + } + break; + } +#endif + default: { + PRINTD (DBG_QOS, "unsupported RX traffic class"); + return -EINVAL; + } + } + } + + + // late abort useful for diagnostics + if (vcc.aal != aal5) { + PRINTD (DBG_QOS, "AAL not supported"); + return -EINVAL; + } + + // get space for our vcc stuff and copy parameters into it + vccp = kmalloc (sizeof(hrz_vcc), GFP_KERNEL); + if (!vccp) { + PRINTK (KERN_ERR, "out of memory!"); + return -ENOMEM; + } + *vccp = vcc; + + // clear error and grab cell rate resource lock + error = 0; + spin_lock (&dev->rate_lock); + + if (vcc.tx_rate > dev->tx_avail) { + PRINTD (DBG_QOS, "not enough TX PCR left"); + error = -EAGAIN; + } + + if (vcc.rx_rate > dev->rx_avail) { + PRINTD (DBG_QOS, "not enough RX PCR left"); + error = -EAGAIN; + } + + if (!error) { + // really consume cell rates + dev->tx_avail -= vcc.tx_rate; + dev->rx_avail -= vcc.rx_rate; + PRINTD (DBG_QOS|DBG_VCC, "reserving %u TX PCR and %u RX PCR", + vcc.tx_rate, vcc.rx_rate); + } + + // release lock and exit on error + spin_unlock (&dev->rate_lock); + if (error) { + PRINTD (DBG_QOS|DBG_VCC, "insufficient cell rate resources"); + kfree (vccp); + return error; + } + + // this is "immediately before allocating the connection identifier + // in hardware" - so long as the next call does not fail :) + set_bit(ATM_VF_ADDR,&atm_vcc->flags); + + // any errors here are very serious and should never occur + + if (rxtp->traffic_class != ATM_NONE) { + if (dev->rxer[channel]) { + PRINTD (DBG_ERR|DBG_VCC, "VC already open for RX"); + error = -EBUSY; + } + if (!error) + error = hrz_open_rx (dev, channel); + if (error) { + kfree (vccp); + return error; + } + // this link allows RX frames through + dev->rxer[channel] = atm_vcc; + } + + // success, set elements of atm_vcc + atm_vcc->dev_data = (void *) vccp; + + // indicate readiness + set_bit(ATM_VF_READY,&atm_vcc->flags); + + return 0; +} + +/********** close VC **********/ + +static void hrz_close (struct atm_vcc * atm_vcc) { + hrz_dev * dev = HRZ_DEV(atm_vcc->dev); + hrz_vcc * vcc = HRZ_VCC(atm_vcc); + u16 channel = vcc->channel; + PRINTD (DBG_VCC|DBG_FLOW, "hrz_close"); + + // indicate unreadiness + clear_bit(ATM_VF_READY,&atm_vcc->flags); + + if (atm_vcc->qos.txtp.traffic_class != ATM_NONE) { + unsigned int i; + + // let any TX on this channel that has started complete + // no restart, just keep trying + while (tx_hold (dev)) + ; + // remove record of any tx_channel having been setup for this channel + for (i = 0; i < TX_CHANS; ++i) + if (dev->tx_channel_record[i] == channel) { + dev->tx_channel_record[i] = -1; + break; + } + if (dev->last_vc == channel) + dev->tx_last = -1; + tx_release (dev); + } + + if (atm_vcc->qos.rxtp.traffic_class != ATM_NONE) { + // disable RXing - it tries quite hard + hrz_close_rx (dev, channel); + // forget the vcc - no more skbs will be pushed + if (atm_vcc != dev->rxer[channel]) + PRINTK (KERN_ERR, "%s atm_vcc=%p rxer[channel]=%p", + "arghhh! we're going to die!", + atm_vcc, dev->rxer[channel]); + dev->rxer[channel] = NULL; + } + + // atomically release our rate reservation + spin_lock (&dev->rate_lock); + PRINTD (DBG_QOS|DBG_VCC, "releasing %u TX PCR and %u RX PCR", + vcc->tx_rate, vcc->rx_rate); + dev->tx_avail += vcc->tx_rate; + dev->rx_avail += vcc->rx_rate; + spin_unlock (&dev->rate_lock); + + // free our structure + kfree (vcc); + // say the VPI/VCI is free again + clear_bit(ATM_VF_ADDR,&atm_vcc->flags); +} + +#if 0 +static int hrz_ioctl (struct atm_dev * atm_dev, unsigned int cmd, void *arg) { + hrz_dev * dev = HRZ_DEV(atm_dev); + PRINTD (DBG_FLOW, "hrz_ioctl"); + return -1; +} + +unsigned char hrz_phy_get (struct atm_dev * atm_dev, unsigned long addr) { + hrz_dev * dev = HRZ_DEV(atm_dev); + PRINTD (DBG_FLOW, "hrz_phy_get"); + return 0; +} + +static void hrz_phy_put (struct atm_dev * atm_dev, unsigned char value, + unsigned long addr) { + hrz_dev * dev = HRZ_DEV(atm_dev); + PRINTD (DBG_FLOW, "hrz_phy_put"); +} + +static int hrz_change_qos (struct atm_vcc * atm_vcc, struct atm_qos *qos, int flgs) { + hrz_dev * dev = HRZ_DEV(vcc->dev); + PRINTD (DBG_FLOW, "hrz_change_qos"); + return -1; +} +#endif + +/********** proc file contents **********/ + +static int hrz_proc_read (struct atm_dev * atm_dev, loff_t * pos, char * page) { + hrz_dev * dev = HRZ_DEV(atm_dev); + int left = *pos; + PRINTD (DBG_FLOW, "hrz_proc_read"); + + /* more diagnostics here? */ + +#if 0 + if (!left--) { + unsigned int count = sprintf (page, "vbr buckets:"); + unsigned int i; + for (i = 0; i < TX_CHANS; ++i) + count += sprintf (page, " %u/%u", + query_tx_channel_config (dev, i, BUCKET_FULLNESS_ACCESS), + query_tx_channel_config (dev, i, BUCKET_CAPACITY_ACCESS)); + count += sprintf (page+count, ".\n"); + return count; + } +#endif + + if (!left--) + return sprintf (page, + "cells: TX %lu, RX %lu, HEC errors %lu, unassigned %lu.\n", + dev->tx_cell_count, dev->rx_cell_count, + dev->hec_error_count, dev->unassigned_cell_count); + + if (!left--) + return sprintf (page, + "free cell buffers: TX %hu, RX %hu+%hu.\n", + rd_regw (dev, TX_FREE_BUFFER_COUNT_OFF), + rd_regw (dev, RX_FREE_BUFFER_COUNT_OFF), + dev->noof_spare_buffers); + + if (!left--) + return sprintf (page, + "cps remaining: TX %u, RX %u\n", + dev->tx_avail, dev->rx_avail); + + return 0; +} + +static const struct atmdev_ops hrz_ops = { + .open = hrz_open, + .close = hrz_close, + .send = hrz_send, + .proc_read = hrz_proc_read, + .owner = THIS_MODULE, +}; + +static int hrz_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_ent) +{ + hrz_dev * dev; + int err = 0; + + // adapter slot free, read resources from PCI configuration space + u32 iobase = pci_resource_start (pci_dev, 0); + u32 * membase = bus_to_virt (pci_resource_start (pci_dev, 1)); + unsigned int irq; + unsigned char lat; + + PRINTD (DBG_FLOW, "hrz_probe"); + + if (pci_enable_device(pci_dev)) + return -EINVAL; + + /* XXX DEV_LABEL is a guess */ + if (!request_region(iobase, HRZ_IO_EXTENT, DEV_LABEL)) { + err = -EINVAL; + goto out_disable; + } + + dev = kzalloc(sizeof(hrz_dev), GFP_KERNEL); + if (!dev) { + // perhaps we should be nice: deregister all adapters and abort? + PRINTD(DBG_ERR, "out of memory"); + err = -ENOMEM; + goto out_release; + } + + pci_set_drvdata(pci_dev, dev); + + // grab IRQ and install handler - move this someplace more sensible + irq = pci_dev->irq; + if (request_irq(irq, + interrupt_handler, + IRQF_SHARED, /* irqflags guess */ + DEV_LABEL, /* name guess */ + dev)) { + PRINTD(DBG_WARN, "request IRQ failed!"); + err = -EINVAL; + goto out_free; + } + + PRINTD(DBG_INFO, "found Madge ATM adapter (hrz) at: IO %x, IRQ %u, MEM %p", + iobase, irq, membase); + + dev->atm_dev = atm_dev_register(DEV_LABEL, &pci_dev->dev, &hrz_ops, -1, + NULL); + if (!(dev->atm_dev)) { + PRINTD(DBG_ERR, "failed to register Madge ATM adapter"); + err = -EINVAL; + goto out_free_irq; + } + + PRINTD(DBG_INFO, "registered Madge ATM adapter (no. %d) (%p) at %p", + dev->atm_dev->number, dev, dev->atm_dev); + dev->atm_dev->dev_data = (void *) dev; + dev->pci_dev = pci_dev; + + // enable bus master accesses + pci_set_master(pci_dev); + + // frobnicate latency (upwards, usually) + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &lat); + if (pci_lat) { + PRINTD(DBG_INFO, "%s PCI latency timer from %hu to %hu", + "changing", lat, pci_lat); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, pci_lat); + } else if (lat < MIN_PCI_LATENCY) { + PRINTK(KERN_INFO, "%s PCI latency timer from %hu to %hu", + "increasing", lat, MIN_PCI_LATENCY); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, MIN_PCI_LATENCY); + } + + dev->iobase = iobase; + dev->irq = irq; + dev->membase = membase; + + dev->rx_q_entry = dev->rx_q_reset = &memmap->rx_q_entries[0]; + dev->rx_q_wrap = &memmap->rx_q_entries[RX_CHANS-1]; + + // these next three are performance hacks + dev->last_vc = -1; + dev->tx_last = -1; + dev->tx_idle = 0; + + dev->tx_regions = 0; + dev->tx_bytes = 0; + dev->tx_skb = NULL; + dev->tx_iovec = NULL; + + dev->tx_cell_count = 0; + dev->rx_cell_count = 0; + dev->hec_error_count = 0; + dev->unassigned_cell_count = 0; + + dev->noof_spare_buffers = 0; + + { + unsigned int i; + for (i = 0; i < TX_CHANS; ++i) + dev->tx_channel_record[i] = -1; + } + + dev->flags = 0; + + // Allocate cell rates and remember ASIC version + // Fibre: ATM_OC3_PCR = 1555200000/8/270*260/53 - 29/53 + // Copper: (WRONG) we want 6 into the above, close to 25Mb/s + // Copper: (plagarise!) 25600000/8/270*260/53 - n/53 + + if (hrz_init(dev)) { + // to be really pedantic, this should be ATM_OC3c_PCR + dev->tx_avail = ATM_OC3_PCR; + dev->rx_avail = ATM_OC3_PCR; + set_bit(ultra, &dev->flags); // NOT "|= ultra" ! + } else { + dev->tx_avail = ((25600000/8)*26)/(27*53); + dev->rx_avail = ((25600000/8)*26)/(27*53); + PRINTD(DBG_WARN, "Buggy ASIC: no TX bus-mastering."); + } + + // rate changes spinlock + spin_lock_init(&dev->rate_lock); + + // on-board memory access spinlock; we want atomic reads and + // writes to adapter memory (handles IRQ and SMP) + spin_lock_init(&dev->mem_lock); + + init_waitqueue_head(&dev->tx_queue); + + // vpi in 0..4, vci in 6..10 + dev->atm_dev->ci_range.vpi_bits = vpi_bits; + dev->atm_dev->ci_range.vci_bits = 10-vpi_bits; + + timer_setup(&dev->housekeeping, do_housekeeping, 0); + mod_timer(&dev->housekeeping, jiffies); + +out: + return err; + +out_free_irq: + free_irq(irq, dev); +out_free: + kfree(dev); +out_release: + release_region(iobase, HRZ_IO_EXTENT); +out_disable: + pci_disable_device(pci_dev); + goto out; +} + +static void hrz_remove_one(struct pci_dev *pci_dev) +{ + hrz_dev *dev; + + dev = pci_get_drvdata(pci_dev); + + PRINTD(DBG_INFO, "closing %p (atm_dev = %p)", dev, dev->atm_dev); + del_timer_sync(&dev->housekeeping); + hrz_reset(dev); + atm_dev_deregister(dev->atm_dev); + free_irq(dev->irq, dev); + release_region(dev->iobase, HRZ_IO_EXTENT); + kfree(dev); + + pci_disable_device(pci_dev); +} + +static void __init hrz_check_args (void) { +#ifdef DEBUG_HORIZON + PRINTK (KERN_NOTICE, "debug bitmap is %hx", debug &= DBG_MASK); +#else + if (debug) + PRINTK (KERN_NOTICE, "no debug support in this image"); +#endif + + if (vpi_bits > HRZ_MAX_VPI) + PRINTK (KERN_ERR, "vpi_bits has been limited to %hu", + vpi_bits = HRZ_MAX_VPI); + + if (max_tx_size < 0 || max_tx_size > TX_AAL5_LIMIT) + PRINTK (KERN_NOTICE, "max_tx_size has been limited to %hu", + max_tx_size = TX_AAL5_LIMIT); + + if (max_rx_size < 0 || max_rx_size > RX_AAL5_LIMIT) + PRINTK (KERN_NOTICE, "max_rx_size has been limited to %hu", + max_rx_size = RX_AAL5_LIMIT); + + return; +} + +MODULE_AUTHOR(maintainer_string); +MODULE_DESCRIPTION(description_string); +MODULE_LICENSE("GPL"); +module_param(debug, ushort, 0644); +module_param(vpi_bits, ushort, 0); +module_param(max_tx_size, int, 0); +module_param(max_rx_size, int, 0); +module_param(pci_lat, byte, 0); +MODULE_PARM_DESC(debug, "debug bitmap, see .h file"); +MODULE_PARM_DESC(vpi_bits, "number of bits (0..4) to allocate to VPIs"); +MODULE_PARM_DESC(max_tx_size, "maximum size of TX AAL5 frames"); +MODULE_PARM_DESC(max_rx_size, "maximum size of RX AAL5 frames"); +MODULE_PARM_DESC(pci_lat, "PCI latency in bus cycles"); + +static const struct pci_device_id hrz_pci_tbl[] = { + { PCI_VENDOR_ID_MADGE, PCI_DEVICE_ID_MADGE_HORIZON, PCI_ANY_ID, PCI_ANY_ID, + 0, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, hrz_pci_tbl); + +static struct pci_driver hrz_driver = { + .name = "horizon", + .probe = hrz_probe, + .remove = hrz_remove_one, + .id_table = hrz_pci_tbl, +}; + +/********** module entry **********/ + +static int __init hrz_module_init (void) { + BUILD_BUG_ON(sizeof(struct MEMMAP) != 128*1024/4); + + show_version(); + + // check arguments + hrz_check_args(); + + // get the juice + return pci_register_driver(&hrz_driver); +} + +/********** module exit **********/ + +static void __exit hrz_module_exit (void) { + PRINTD (DBG_FLOW, "cleanup_module"); + + pci_unregister_driver(&hrz_driver); +} + +module_init(hrz_module_init); +module_exit(hrz_module_exit); diff --git a/drivers/atm/horizon.h b/drivers/atm/horizon.h new file mode 100644 index 000000000..7523eba19 --- /dev/null +++ b/drivers/atm/horizon.h @@ -0,0 +1,492 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Madge Horizon ATM Adapter driver. + Copyright (C) 1995-1999 Madge Networks Ltd. + +*/ + +/* + IMPORTANT NOTE: Madge Networks no longer makes the adapters + supported by this driver and makes no commitment to maintain it. +*/ + +/* too many macros - change to inline functions */ + +#ifndef DRIVER_ATM_HORIZON_H +#define DRIVER_ATM_HORIZON_H + + +#ifdef CONFIG_ATM_HORIZON_DEBUG +#define DEBUG_HORIZON +#endif + +#define DEV_LABEL "hrz" + +#ifndef PCI_VENDOR_ID_MADGE +#define PCI_VENDOR_ID_MADGE 0x10B6 +#endif +#ifndef PCI_DEVICE_ID_MADGE_HORIZON +#define PCI_DEVICE_ID_MADGE_HORIZON 0x1000 +#endif + +// diagnostic output + +#define PRINTK(severity,format,args...) \ + printk(severity DEV_LABEL ": " format "\n" , ## args) + +#ifdef DEBUG_HORIZON + +#define DBG_ERR 0x0001 +#define DBG_WARN 0x0002 +#define DBG_INFO 0x0004 +#define DBG_VCC 0x0008 +#define DBG_QOS 0x0010 +#define DBG_TX 0x0020 +#define DBG_RX 0x0040 +#define DBG_SKB 0x0080 +#define DBG_IRQ 0x0100 +#define DBG_FLOW 0x0200 +#define DBG_BUS 0x0400 +#define DBG_REGS 0x0800 +#define DBG_DATA 0x1000 +#define DBG_MASK 0x1fff + +/* the ## prevents the annoying double expansion of the macro arguments */ +/* KERN_INFO is used since KERN_DEBUG often does not make it to the console */ +#define PRINTDB(bits,format,args...) \ + ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format , ## args) : 1 ) +#define PRINTDM(bits,format,args...) \ + ( (debug & (bits)) ? printk (format , ## args) : 1 ) +#define PRINTDE(bits,format,args...) \ + ( (debug & (bits)) ? printk (format "\n" , ## args) : 1 ) +#define PRINTD(bits,format,args...) \ + ( (debug & (bits)) ? printk (KERN_INFO DEV_LABEL ": " format "\n" , ## args) : 1 ) + +#else + +#define PRINTD(bits,format,args...) +#define PRINTDB(bits,format,args...) +#define PRINTDM(bits,format,args...) +#define PRINTDE(bits,format,args...) + +#endif + +#define PRINTDD(sec,fmt,args...) +#define PRINTDDB(sec,fmt,args...) +#define PRINTDDM(sec,fmt,args...) +#define PRINTDDE(sec,fmt,args...) + +// fixed constants + +#define SPARE_BUFFER_POOL_SIZE MAX_VCS +#define HRZ_MAX_VPI 4 +#define MIN_PCI_LATENCY 48 // 24 IS TOO SMALL + +/* Horizon specific bits */ +/* Register offsets */ + +#define HRZ_IO_EXTENT 0x80 + +#define DATA_PORT_OFF 0x00 +#define TX_CHANNEL_PORT_OFF 0x04 +#define TX_DESCRIPTOR_PORT_OFF 0x08 +#define MEMORY_PORT_OFF 0x0C +#define MEM_WR_ADDR_REG_OFF 0x14 +#define MEM_RD_ADDR_REG_OFF 0x18 +#define CONTROL_0_REG 0x1C +#define INT_SOURCE_REG_OFF 0x20 +#define INT_ENABLE_REG_OFF 0x24 +#define MASTER_RX_ADDR_REG_OFF 0x28 +#define MASTER_RX_COUNT_REG_OFF 0x2C +#define MASTER_TX_ADDR_REG_OFF 0x30 +#define MASTER_TX_COUNT_REG_OFF 0x34 +#define TX_DESCRIPTOR_REG_OFF 0x38 +#define TX_CHANNEL_CONFIG_COMMAND_OFF 0x40 +#define TX_CHANNEL_CONFIG_DATA_OFF 0x44 +#define TX_FREE_BUFFER_COUNT_OFF 0x48 +#define RX_FREE_BUFFER_COUNT_OFF 0x4C +#define TX_CONFIG_OFF 0x50 +#define TX_STATUS_OFF 0x54 +#define RX_CONFIG_OFF 0x58 +#define RX_LINE_CONFIG_OFF 0x5C +#define RX_QUEUE_RD_PTR_OFF 0x60 +#define RX_QUEUE_WR_PTR_OFF 0x64 +#define MAX_AAL5_CELL_COUNT_OFF 0x68 +#define RX_CHANNEL_PORT_OFF 0x6C +#define TX_CELL_COUNT_OFF 0x70 +#define RX_CELL_COUNT_OFF 0x74 +#define HEC_ERROR_COUNT_OFF 0x78 +#define UNASSIGNED_CELL_COUNT_OFF 0x7C + +/* Register bit definitions */ + +/* Control 0 register */ + +#define SEEPROM_DO 0x00000001 +#define SEEPROM_DI 0x00000002 +#define SEEPROM_SK 0x00000004 +#define SEEPROM_CS 0x00000008 +#define DEBUG_BIT_0 0x00000010 +#define DEBUG_BIT_1 0x00000020 +#define DEBUG_BIT_2 0x00000040 +// RESERVED 0x00000080 +#define DEBUG_BIT_0_OE 0x00000100 +#define DEBUG_BIT_1_OE 0x00000200 +#define DEBUG_BIT_2_OE 0x00000400 +// RESERVED 0x00000800 +#define DEBUG_BIT_0_STATE 0x00001000 +#define DEBUG_BIT_1_STATE 0x00002000 +#define DEBUG_BIT_2_STATE 0x00004000 +// RESERVED 0x00008000 +#define GENERAL_BIT_0 0x00010000 +#define GENERAL_BIT_1 0x00020000 +#define GENERAL_BIT_2 0x00040000 +#define GENERAL_BIT_3 0x00080000 +#define RESET_HORIZON 0x00100000 +#define RESET_ATM 0x00200000 +#define RESET_RX 0x00400000 +#define RESET_TX 0x00800000 +#define RESET_HOST 0x01000000 +// RESERVED 0x02000000 +#define TARGET_RETRY_DISABLE 0x04000000 +#define ATM_LAYER_SELECT 0x08000000 +#define ATM_LAYER_STATUS 0x10000000 +// RESERVED 0xE0000000 + +/* Interrupt source and enable registers */ + +#define RX_DATA_AV 0x00000001 +#define RX_DISABLED 0x00000002 +#define TIMING_MARKER 0x00000004 +#define FORCED 0x00000008 +#define RX_BUS_MASTER_COMPLETE 0x00000010 +#define TX_BUS_MASTER_COMPLETE 0x00000020 +#define ABR_TX_CELL_COUNT_INT 0x00000040 +#define DEBUG_INT 0x00000080 +// RESERVED 0xFFFFFF00 + +/* PIO and Bus Mastering */ + +#define MAX_PIO_COUNT 0x000000ff // 255 - make tunable? +// 8188 is a hard limit for bus mastering +#define MAX_TRANSFER_COUNT 0x00001ffc // 8188 +#define MASTER_TX_AUTO_APPEND_DESC 0x80000000 + +/* TX channel config command port */ + +#define PCR_TIMER_ACCESS 0x0000 +#define SCR_TIMER_ACCESS 0x0001 +#define BUCKET_CAPACITY_ACCESS 0x0002 +#define BUCKET_FULLNESS_ACCESS 0x0003 +#define RATE_TYPE_ACCESS 0x0004 +// UNUSED 0x00F8 +#define TX_CHANNEL_CONFIG_MULT 0x0100 +// UNUSED 0xF800 +#define BUCKET_MAX_SIZE 0x003f + +/* TX channel config data port */ + +#define CLOCK_SELECT_SHIFT 4 +#define CLOCK_DISABLE 0x00ff + +#define IDLE_RATE_TYPE 0x0 +#define ABR_RATE_TYPE 0x1 +#define VBR_RATE_TYPE 0x2 +#define CBR_RATE_TYPE 0x3 + +/* TX config register */ + +#define DRVR_DRVRBAR_ENABLE 0x0001 +#define TXCLK_MUX_SELECT_RCLK 0x0002 +#define TRANSMIT_TIMING_MARKER 0x0004 +#define LOOPBACK_TIMING_MARKER 0x0008 +#define TX_TEST_MODE_16MHz 0x0000 +#define TX_TEST_MODE_8MHz 0x0010 +#define TX_TEST_MODE_5_33MHz 0x0020 +#define TX_TEST_MODE_4MHz 0x0030 +#define TX_TEST_MODE_3_2MHz 0x0040 +#define TX_TEST_MODE_2_66MHz 0x0050 +#define TX_TEST_MODE_2_29MHz 0x0060 +#define TX_NORMAL_OPERATION 0x0070 +#define ABR_ROUND_ROBIN 0x0080 + +/* TX status register */ + +#define IDLE_CHANNELS_MASK 0x00FF +#define ABR_CELL_COUNT_REACHED_MULT 0x0100 +#define ABR_CELL_COUNT_REACHED_MASK 0xFF + +/* RX config register */ + +#define NON_USER_CELLS_IN_ONE_CHANNEL 0x0008 +#define RX_ENABLE 0x0010 +#define IGNORE_UNUSED_VPI_VCI_BITS_SET 0x0000 +#define NON_USER_UNUSED_VPI_VCI_BITS_SET 0x0020 +#define DISCARD_UNUSED_VPI_VCI_BITS_SET 0x0040 + +/* RX line config register */ + +#define SIGNAL_LOSS 0x0001 +#define FREQUENCY_DETECT_ERROR 0x0002 +#define LOCK_DETECT_ERROR 0x0004 +#define SELECT_INTERNAL_LOOPBACK 0x0008 +#define LOCK_DETECT_ENABLE 0x0010 +#define FREQUENCY_DETECT_ENABLE 0x0020 +#define USER_FRAQ 0x0040 +#define GXTALOUT_SELECT_DIV4 0x0080 +#define GXTALOUT_SELECT_NO_GATING 0x0100 +#define TIMING_MARKER_RECEIVED 0x0200 + +/* RX channel port */ + +#define RX_CHANNEL_MASK 0x03FF +// UNUSED 0x3C00 +#define FLUSH_CHANNEL 0x4000 +#define RX_CHANNEL_UPDATE_IN_PROGRESS 0x8000 + +/* Receive queue entry */ + +#define RX_Q_ENTRY_LENGTH_MASK 0x0000FFFF +#define RX_Q_ENTRY_CHANNEL_SHIFT 16 +#define SIMONS_DODGEY_MARKER 0x08000000 +#define RX_CONGESTION_EXPERIENCED 0x10000000 +#define RX_CRC_10_OK 0x20000000 +#define RX_CRC_32_OK 0x40000000 +#define RX_COMPLETE_FRAME 0x80000000 + +/* Offsets and constants for use with the buffer memory */ + +/* Buffer pointers and channel types */ + +#define BUFFER_PTR_MASK 0x0000FFFF +#define RX_INT_THRESHOLD_MULT 0x00010000 +#define RX_INT_THRESHOLD_MASK 0x07FF +#define INT_EVERY_N_CELLS 0x08000000 +#define CONGESTION_EXPERIENCED 0x10000000 +#define FIRST_CELL_OF_AAL5_FRAME 0x20000000 +#define CHANNEL_TYPE_AAL5 0x00000000 +#define CHANNEL_TYPE_RAW_CELLS 0x40000000 +#define CHANNEL_TYPE_AAL3_4 0x80000000 + +/* Buffer status stuff */ + +#define BUFF_STATUS_MASK 0x00030000 +#define BUFF_STATUS_EMPTY 0x00000000 +#define BUFF_STATUS_CELL_AV 0x00010000 +#define BUFF_STATUS_LAST_CELL_AV 0x00020000 + +/* Transmit channel stuff */ + +/* Receive channel stuff */ + +#define RX_CHANNEL_DISABLED 0x00000000 +#define RX_CHANNEL_IDLE 0x00000001 + +/* General things */ + +#define INITIAL_CRC 0xFFFFFFFF + +// A Horizon u32, a byte! Really nasty. Horizon pointers are (32 bit) +// word addresses and so standard C pointer operations break (as they +// assume byte addresses); so we pretend that Horizon words (and word +// pointers) are bytes (and byte pointers) for the purposes of having +// a memory map that works. + +typedef u8 HDW; + +typedef struct cell_buf { + HDW payload[12]; + HDW next; + HDW cell_count; // AAL5 rx bufs + HDW res; + union { + HDW partial_crc; // AAL5 rx bufs + HDW cell_header; // RAW bufs + } u; +} cell_buf; + +typedef struct tx_ch_desc { + HDW rd_buf_type; + HDW wr_buf_type; + HDW partial_crc; + HDW cell_header; +} tx_ch_desc; + +typedef struct rx_ch_desc { + HDW wr_buf_type; + HDW rd_buf_type; +} rx_ch_desc; + +typedef struct rx_q_entry { + HDW entry; +} rx_q_entry; + +#define TX_CHANS 8 +#define RX_CHANS 1024 +#define RX_QS 1024 +#define MAX_VCS RX_CHANS + +/* Horizon buffer memory map */ + +// TX Channel Descriptors 2 +// TX Initial Buffers 8 // TX_CHANS +#define BUFN1_SIZE 118 // (126 - TX_CHANS) +// RX/TX Start/End Buffers 4 +#define BUFN2_SIZE 124 +// RX Queue Entries 64 +#define BUFN3_SIZE 192 +// RX Channel Descriptors 128 +#define BUFN4_SIZE 1408 +// TOTAL cell_buff chunks 2048 + +// cell_buf bufs[2048]; +// HDW dws[32768]; + +typedef struct MEMMAP { + tx_ch_desc tx_descs[TX_CHANS]; // 8 * 4 = 32 , 0x0020 + cell_buf inittxbufs[TX_CHANS]; // these are really + cell_buf bufn1[BUFN1_SIZE]; // part of this pool + cell_buf txfreebufstart; + cell_buf txfreebufend; + cell_buf rxfreebufstart; + cell_buf rxfreebufend; // 8+118+1+1+1+1+124 = 254 + cell_buf bufn2[BUFN2_SIZE]; // 16 * 254 = 4064 , 0x1000 + rx_q_entry rx_q_entries[RX_QS]; // 1 * 1024 = 1024 , 0x1400 + cell_buf bufn3[BUFN3_SIZE]; // 16 * 192 = 3072 , 0x2000 + rx_ch_desc rx_descs[MAX_VCS]; // 2 * 1024 = 2048 , 0x2800 + cell_buf bufn4[BUFN4_SIZE]; // 16 * 1408 = 22528 , 0x8000 +} MEMMAP; + +#define memmap ((MEMMAP *)0) + +/* end horizon specific bits */ + +typedef enum { + aal0, + aal34, + aal5 +} hrz_aal; + +typedef enum { + tx_busy, + rx_busy, + ultra +} hrz_flags; + +// a single struct pointed to by atm_vcc->dev_data + +typedef struct { + unsigned int tx_rate; + unsigned int rx_rate; + u16 channel; + u16 tx_xbr_bits; + u16 tx_pcr_bits; +#if 0 + u16 tx_scr_bits; + u16 tx_bucket_bits; +#endif + hrz_aal aal; +} hrz_vcc; + +struct hrz_dev { + + u32 iobase; + u32 * membase; + + struct sk_buff * rx_skb; // skb being RXed + unsigned int rx_bytes; // bytes remaining to RX within region + void * rx_addr; // addr to send bytes to (for PIO) + unsigned int rx_channel; // channel that the skb is going out on + + struct sk_buff * tx_skb; // skb being TXed + unsigned int tx_bytes; // bytes remaining to TX within region + void * tx_addr; // addr to send bytes from (for PIO) + struct iovec * tx_iovec; // remaining regions + unsigned int tx_regions; // number of remaining regions + + spinlock_t mem_lock; + wait_queue_head_t tx_queue; + + u8 irq; + unsigned long flags; + u8 tx_last; + u8 tx_idle; + + rx_q_entry * rx_q_reset; + rx_q_entry * rx_q_entry; + rx_q_entry * rx_q_wrap; + + struct atm_dev * atm_dev; + + u32 last_vc; + + int noof_spare_buffers; + u16 spare_buffers[SPARE_BUFFER_POOL_SIZE]; + + u16 tx_channel_record[TX_CHANS]; + + // this is what we follow when we get incoming data + u32 txer[MAX_VCS/32]; + struct atm_vcc * rxer[MAX_VCS]; + + // cell rate allocation + spinlock_t rate_lock; + unsigned int rx_avail; + unsigned int tx_avail; + + // dev stats + unsigned long tx_cell_count; + unsigned long rx_cell_count; + unsigned long hec_error_count; + unsigned long unassigned_cell_count; + + struct pci_dev * pci_dev; + struct timer_list housekeeping; +}; + +typedef struct hrz_dev hrz_dev; + +/* macros for use later */ + +#define BUF_PTR(cbptr) ((cbptr) - (cell_buf *) 0) + +#define INTERESTING_INTERRUPTS \ + (RX_DATA_AV | RX_DISABLED | TX_BUS_MASTER_COMPLETE | RX_BUS_MASTER_COMPLETE) + +// 190 cells by default (192 TX buffers - 2 elbow room, see docs) +#define TX_AAL5_LIMIT (190*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER) // 9112 + +// Have enough RX buffers (unless we allow other buffer splits) +#define RX_AAL5_LIMIT ATM_MAX_AAL5_PDU + +/* multi-statement macro protector */ +#define DW(x) do{ x } while(0) + +#define HRZ_DEV(atm_dev) ((hrz_dev *) (atm_dev)->dev_data) +#define HRZ_VCC(atm_vcc) ((hrz_vcc *) (atm_vcc)->dev_data) + +/* Turn the LEDs on and off */ +// The LEDs bits are upside down in that setting the bit in the debug +// register will turn the appropriate LED off. + +#define YELLOW_LED DEBUG_BIT_0 +#define GREEN_LED DEBUG_BIT_1 +#define YELLOW_LED_OE DEBUG_BIT_0_OE +#define GREEN_LED_OE DEBUG_BIT_1_OE + +#define GREEN_LED_OFF(dev) \ + wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) | GREEN_LED) +#define GREEN_LED_ON(dev) \ + wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) &~ GREEN_LED) +#define YELLOW_LED_OFF(dev) \ + wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) | YELLOW_LED) +#define YELLOW_LED_ON(dev) \ + wr_regl (dev, CONTROL_0_REG, rd_regl (dev, CONTROL_0_REG) &~ YELLOW_LED) + +typedef enum { + round_up, + round_down, + round_nearest +} rounding; + +#endif /* DRIVER_ATM_HORIZON_H */ diff --git a/drivers/atm/idt77105.c b/drivers/atm/idt77105.c new file mode 100644 index 000000000..bfca7b8a6 --- /dev/null +++ b/drivers/atm/idt77105.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/atm/idt77105.c - IDT77105 (PHY) driver */ + +/* Written 1999 by Greg Banks, NEC Australia . Based on suni.c */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "idt77105.h" + +#undef GENERAL_DEBUG + +#ifdef GENERAL_DEBUG +#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) +#else +#define DPRINTK(format,args...) +#endif + + +struct idt77105_priv { + struct idt77105_stats stats; /* link diagnostics */ + struct atm_dev *dev; /* device back-pointer */ + struct idt77105_priv *next; + int loop_mode; + unsigned char old_mcr; /* storage of MCR reg while signal lost */ +}; + +static DEFINE_SPINLOCK(idt77105_priv_lock); + +#define PRIV(dev) ((struct idt77105_priv *) dev->phy_data) + +#define PUT(val,reg) dev->ops->phy_put(dev,val,IDT77105_##reg) +#define GET(reg) dev->ops->phy_get(dev,IDT77105_##reg) + +static void idt77105_stats_timer_func(struct timer_list *); +static void idt77105_restart_timer_func(struct timer_list *); + + +static DEFINE_TIMER(stats_timer, idt77105_stats_timer_func); +static DEFINE_TIMER(restart_timer, idt77105_restart_timer_func); +static int start_timer = 1; +static struct idt77105_priv *idt77105_all = NULL; + +/* + * Retrieve the value of one of the IDT77105's counters. + * `counter' is one of the IDT77105_CTRSEL_* constants. + */ +static u16 get_counter(struct atm_dev *dev, int counter) +{ + u16 val; + + /* write the counter bit into PHY register 6 */ + PUT(counter, CTRSEL); + /* read the low 8 bits from register 4 */ + val = GET(CTRLO); + /* read the high 8 bits from register 5 */ + val |= GET(CTRHI)<<8; + + return val; +} + +/* + * Timer function called every second to gather statistics + * from the 77105. This is done because the h/w registers + * will overflow if not read at least once per second. The + * kernel's stats are much higher precision. Also, having + * a separate copy of the stats allows implementation of + * an ioctl which gathers the stats *without* zero'ing them. + */ +static void idt77105_stats_timer_func(struct timer_list *unused) +{ + struct idt77105_priv *walk; + struct atm_dev *dev; + struct idt77105_stats *stats; + + DPRINTK("IDT77105 gathering statistics\n"); + for (walk = idt77105_all; walk; walk = walk->next) { + dev = walk->dev; + + stats = &walk->stats; + stats->symbol_errors += get_counter(dev, IDT77105_CTRSEL_SEC); + stats->tx_cells += get_counter(dev, IDT77105_CTRSEL_TCC); + stats->rx_cells += get_counter(dev, IDT77105_CTRSEL_RCC); + stats->rx_hec_errors += get_counter(dev, IDT77105_CTRSEL_RHEC); + } + if (!start_timer) mod_timer(&stats_timer,jiffies+IDT77105_STATS_TIMER_PERIOD); +} + + +/* + * A separate timer func which handles restarting PHY chips which + * have had the cable re-inserted after being pulled out. This is + * done by polling the Good Signal Bit in the Interrupt Status + * register every 5 seconds. The other technique (checking Good + * Signal Bit in the interrupt handler) cannot be used because PHY + * interrupts need to be disabled when the cable is pulled out + * to avoid lots of spurious cell error interrupts. + */ +static void idt77105_restart_timer_func(struct timer_list *unused) +{ + struct idt77105_priv *walk; + struct atm_dev *dev; + unsigned char istat; + + DPRINTK("IDT77105 checking for cable re-insertion\n"); + for (walk = idt77105_all; walk; walk = walk->next) { + dev = walk->dev; + + if (dev->signal != ATM_PHY_SIG_LOST) + continue; + + istat = GET(ISTAT); /* side effect: clears all interrupt status bits */ + if (istat & IDT77105_ISTAT_GOODSIG) { + /* Found signal again */ + atm_dev_signal_change(dev, ATM_PHY_SIG_FOUND); + printk(KERN_NOTICE "%s(itf %d): signal detected again\n", + dev->type,dev->number); + /* flush the receive FIFO */ + PUT( GET(DIAG) | IDT77105_DIAG_RFLUSH, DIAG); + /* re-enable interrupts */ + PUT( walk->old_mcr ,MCR); + } + } + if (!start_timer) mod_timer(&restart_timer,jiffies+IDT77105_RESTART_TIMER_PERIOD); +} + + +static int fetch_stats(struct atm_dev *dev,struct idt77105_stats __user *arg,int zero) +{ + unsigned long flags; + struct idt77105_stats stats; + + spin_lock_irqsave(&idt77105_priv_lock, flags); + memcpy(&stats, &PRIV(dev)->stats, sizeof(struct idt77105_stats)); + if (zero) + memset(&PRIV(dev)->stats, 0, sizeof(struct idt77105_stats)); + spin_unlock_irqrestore(&idt77105_priv_lock, flags); + if (arg == NULL) + return 0; + return copy_to_user(arg, &stats, + sizeof(struct idt77105_stats)) ? -EFAULT : 0; +} + + +static int set_loopback(struct atm_dev *dev,int mode) +{ + int diag; + + diag = GET(DIAG) & ~IDT77105_DIAG_LCMASK; + switch (mode) { + case ATM_LM_NONE: + break; + case ATM_LM_LOC_ATM: + diag |= IDT77105_DIAG_LC_PHY_LOOPBACK; + break; + case ATM_LM_RMT_ATM: + diag |= IDT77105_DIAG_LC_LINE_LOOPBACK; + break; + default: + return -EINVAL; + } + PUT(diag,DIAG); + printk(KERN_NOTICE "%s(%d) Loopback mode is: %s\n", dev->type, + dev->number, + (mode == ATM_LM_NONE ? "NONE" : + (mode == ATM_LM_LOC_ATM ? "DIAG (local)" : + (mode == IDT77105_DIAG_LC_LINE_LOOPBACK ? "LOOP (remote)" : + "unknown"))) + ); + PRIV(dev)->loop_mode = mode; + return 0; +} + + +static int idt77105_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + printk(KERN_NOTICE "%s(%d) idt77105_ioctl() called\n",dev->type,dev->number); + switch (cmd) { + case IDT77105_GETSTATZ: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + fallthrough; + case IDT77105_GETSTAT: + return fetch_stats(dev, arg, cmd == IDT77105_GETSTATZ); + case ATM_SETLOOP: + return set_loopback(dev,(int)(unsigned long) arg); + case ATM_GETLOOP: + return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ? + -EFAULT : 0; + case ATM_QUERYLOOP: + return put_user(ATM_LM_LOC_ATM | ATM_LM_RMT_ATM, + (int __user *) arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + + + +static void idt77105_int(struct atm_dev *dev) +{ + unsigned char istat; + + istat = GET(ISTAT); /* side effect: clears all interrupt status bits */ + + DPRINTK("IDT77105 generated an interrupt, istat=%02x\n", (unsigned)istat); + + if (istat & IDT77105_ISTAT_RSCC) { + /* Rx Signal Condition Change - line went up or down */ + if (istat & IDT77105_ISTAT_GOODSIG) { /* signal detected again */ + /* This should not happen (restart timer does it) but JIC */ + atm_dev_signal_change(dev, ATM_PHY_SIG_FOUND); + } else { /* signal lost */ + /* + * Disable interrupts and stop all transmission and + * reception - the restart timer will restore these. + */ + PRIV(dev)->old_mcr = GET(MCR); + PUT( + (PRIV(dev)->old_mcr| + IDT77105_MCR_DREC| + IDT77105_MCR_DRIC| + IDT77105_MCR_HALTTX + ) & ~IDT77105_MCR_EIP, MCR); + atm_dev_signal_change(dev, ATM_PHY_SIG_LOST); + printk(KERN_NOTICE "%s(itf %d): signal lost\n", + dev->type,dev->number); + } + } + + if (istat & IDT77105_ISTAT_RFO) { + /* Rx FIFO Overrun -- perform a FIFO flush */ + PUT( GET(DIAG) | IDT77105_DIAG_RFLUSH, DIAG); + printk(KERN_NOTICE "%s(itf %d): receive FIFO overrun\n", + dev->type,dev->number); + } +#ifdef GENERAL_DEBUG + if (istat & (IDT77105_ISTAT_HECERR | IDT77105_ISTAT_SCR | + IDT77105_ISTAT_RSE)) { + /* normally don't care - just report in stats */ + printk(KERN_NOTICE "%s(itf %d): received cell with error\n", + dev->type,dev->number); + } +#endif +} + + +static int idt77105_start(struct atm_dev *dev) +{ + unsigned long flags; + + if (!(dev->phy_data = kmalloc(sizeof(struct idt77105_priv),GFP_KERNEL))) + return -ENOMEM; + PRIV(dev)->dev = dev; + spin_lock_irqsave(&idt77105_priv_lock, flags); + PRIV(dev)->next = idt77105_all; + idt77105_all = PRIV(dev); + spin_unlock_irqrestore(&idt77105_priv_lock, flags); + memset(&PRIV(dev)->stats,0,sizeof(struct idt77105_stats)); + + /* initialise dev->signal from Good Signal Bit */ + atm_dev_signal_change(dev, + GET(ISTAT) & IDT77105_ISTAT_GOODSIG ? + ATM_PHY_SIG_FOUND : ATM_PHY_SIG_LOST); + if (dev->signal == ATM_PHY_SIG_LOST) + printk(KERN_WARNING "%s(itf %d): no signal\n",dev->type, + dev->number); + + /* initialise loop mode from hardware */ + switch ( GET(DIAG) & IDT77105_DIAG_LCMASK ) { + case IDT77105_DIAG_LC_NORMAL: + PRIV(dev)->loop_mode = ATM_LM_NONE; + break; + case IDT77105_DIAG_LC_PHY_LOOPBACK: + PRIV(dev)->loop_mode = ATM_LM_LOC_ATM; + break; + case IDT77105_DIAG_LC_LINE_LOOPBACK: + PRIV(dev)->loop_mode = ATM_LM_RMT_ATM; + break; + } + + /* enable interrupts, e.g. on loss of signal */ + PRIV(dev)->old_mcr = GET(MCR); + if (dev->signal == ATM_PHY_SIG_FOUND) { + PRIV(dev)->old_mcr |= IDT77105_MCR_EIP; + PUT(PRIV(dev)->old_mcr, MCR); + } + + + idt77105_stats_timer_func(0); /* clear 77105 counters */ + (void) fetch_stats(dev,NULL,1); /* clear kernel counters */ + + spin_lock_irqsave(&idt77105_priv_lock, flags); + if (start_timer) { + start_timer = 0; + + stats_timer.expires = jiffies+IDT77105_STATS_TIMER_PERIOD; + add_timer(&stats_timer); + + restart_timer.expires = jiffies+IDT77105_RESTART_TIMER_PERIOD; + add_timer(&restart_timer); + } + spin_unlock_irqrestore(&idt77105_priv_lock, flags); + return 0; +} + + +static int idt77105_stop(struct atm_dev *dev) +{ + struct idt77105_priv *walk, *prev; + + DPRINTK("%s(itf %d): stopping IDT77105\n",dev->type,dev->number); + + /* disable interrupts */ + PUT( GET(MCR) & ~IDT77105_MCR_EIP, MCR ); + + /* detach private struct from atm_dev & free */ + for (prev = NULL, walk = idt77105_all ; + walk != NULL; + prev = walk, walk = walk->next) { + if (walk->dev == dev) { + if (prev != NULL) + prev->next = walk->next; + else + idt77105_all = walk->next; + dev->phy = NULL; + dev->phy_data = NULL; + kfree(walk); + break; + } + } + + return 0; +} + + +static const struct atmphy_ops idt77105_ops = { + .start = idt77105_start, + .ioctl = idt77105_ioctl, + .interrupt = idt77105_int, + .stop = idt77105_stop, +}; + + +int idt77105_init(struct atm_dev *dev) +{ + dev->phy = &idt77105_ops; + return 0; +} + +EXPORT_SYMBOL(idt77105_init); + +static void __exit idt77105_exit(void) +{ + /* turn off timers */ + del_timer_sync(&stats_timer); + del_timer_sync(&restart_timer); +} + +module_exit(idt77105_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/atm/idt77105.h b/drivers/atm/idt77105.h new file mode 100644 index 000000000..8dfea9e36 --- /dev/null +++ b/drivers/atm/idt77105.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/idt77105.h - IDT77105 (PHY) declarations */ + +/* Written 1999 by Greg Banks, NEC Australia . Based on suni.h */ + + +#ifndef DRIVER_ATM_IDT77105_H +#define DRIVER_ATM_IDT77105_H + +#include +#include + + +/* IDT77105 registers */ + +#define IDT77105_MCR 0x0 /* Master Control Register */ +#define IDT77105_ISTAT 0x1 /* Interrupt Status */ +#define IDT77105_DIAG 0x2 /* Diagnostic Control */ +#define IDT77105_LEDHEC 0x3 /* LED Driver & HEC Status/Control */ +#define IDT77105_CTRLO 0x4 /* Low Byte Counter Register */ +#define IDT77105_CTRHI 0x5 /* High Byte Counter Register */ +#define IDT77105_CTRSEL 0x6 /* Counter Register Read Select */ + +/* IDT77105 register values */ + +/* MCR */ +#define IDT77105_MCR_UPLO 0x80 /* R/W, User Prog'le Output Latch */ +#define IDT77105_MCR_DREC 0x40 /* R/W, Discard Receive Error Cells */ +#define IDT77105_MCR_ECEIO 0x20 /* R/W, Enable Cell Error Interrupts + * Only */ +#define IDT77105_MCR_TDPC 0x10 /* R/W, Transmit Data Parity Check */ +#define IDT77105_MCR_DRIC 0x08 /* R/W, Discard Received Idle Cells */ +#define IDT77105_MCR_HALTTX 0x04 /* R/W, Halt Tx */ +#define IDT77105_MCR_UMODE 0x02 /* R/W, Utopia (cell/byte) Mode */ +#define IDT77105_MCR_EIP 0x01 /* R/W, Enable Interrupt Pin */ + +/* ISTAT */ +#define IDT77105_ISTAT_GOODSIG 0x40 /* R, Good Signal Bit */ +#define IDT77105_ISTAT_HECERR 0x20 /* sticky, HEC Error*/ +#define IDT77105_ISTAT_SCR 0x10 /* sticky, Short Cell Received */ +#define IDT77105_ISTAT_TPE 0x08 /* sticky, Transmit Parity Error */ +#define IDT77105_ISTAT_RSCC 0x04 /* sticky, Rx Signal Condition Change */ +#define IDT77105_ISTAT_RSE 0x02 /* sticky, Rx Symbol Error */ +#define IDT77105_ISTAT_RFO 0x01 /* sticky, Rx FIFO Overrun */ + +/* DIAG */ +#define IDT77105_DIAG_FTD 0x80 /* R/W, Force TxClav deassert */ +#define IDT77105_DIAG_ROS 0x40 /* R/W, RxClav operation select */ +#define IDT77105_DIAG_MPCS 0x20 /* R/W, Multi-PHY config'n select */ +#define IDT77105_DIAG_RFLUSH 0x10 /* R/W, clear receive FIFO */ +#define IDT77105_DIAG_ITPE 0x08 /* R/W, Insert Tx payload error */ +#define IDT77105_DIAG_ITHE 0x04 /* R/W, Insert Tx HEC error */ +#define IDT77105_DIAG_UMODE 0x02 /* R/W, Utopia (cell/byte) Mode */ +#define IDT77105_DIAG_LCMASK 0x03 /* R/W, Loopback Control */ + +#define IDT77105_DIAG_LC_NORMAL 0x00 /* Receive from network */ +#define IDT77105_DIAG_LC_PHY_LOOPBACK 0x02 +#define IDT77105_DIAG_LC_LINE_LOOPBACK 0x03 + +/* LEDHEC */ +#define IDT77105_LEDHEC_DRHC 0x40 /* R/W, Disable Rx HEC check */ +#define IDT77105_LEDHEC_DTHC 0x20 /* R/W, Disable Tx HEC calculation */ +#define IDT77105_LEDHEC_RPWMASK 0x18 /* R/W, RxRef pulse width select */ +#define IDT77105_LEDHEC_TFS 0x04 /* R, Tx FIFO Status (1=empty) */ +#define IDT77105_LEDHEC_TLS 0x02 /* R, Tx LED Status (1=lit) */ +#define IDT77105_LEDHEC_RLS 0x01 /* R, Rx LED Status (1=lit) */ + +#define IDT77105_LEDHEC_RPW_1 0x00 /* RxRef active for 1 RxClk cycle */ +#define IDT77105_LEDHEC_RPW_2 0x08 /* RxRef active for 2 RxClk cycle */ +#define IDT77105_LEDHEC_RPW_4 0x10 /* RxRef active for 4 RxClk cycle */ +#define IDT77105_LEDHEC_RPW_8 0x18 /* RxRef active for 8 RxClk cycle */ + +/* CTRSEL */ +#define IDT77105_CTRSEL_SEC 0x08 /* W, Symbol Error Counter */ +#define IDT77105_CTRSEL_TCC 0x04 /* W, Tx Cell Counter */ +#define IDT77105_CTRSEL_RCC 0x02 /* W, Rx Cell Counter */ +#define IDT77105_CTRSEL_RHEC 0x01 /* W, Rx HEC Error Counter */ + +#ifdef __KERNEL__ +int idt77105_init(struct atm_dev *dev); +#endif + +/* + * Tunable parameters + */ + +/* Time between samples of the hardware cell counters. Should be <= 1 sec */ +#define IDT77105_STATS_TIMER_PERIOD (HZ) +/* Time between checks to see if the signal has been found again */ +#define IDT77105_RESTART_TIMER_PERIOD (5 * HZ) + +#endif diff --git a/drivers/atm/idt77252.c b/drivers/atm/idt77252.c new file mode 100644 index 000000000..a217b5043 --- /dev/null +++ b/drivers/atm/idt77252.c @@ -0,0 +1,3806 @@ +/******************************************************************* + * + * Copyright (c) 2000 ATecoM GmbH + * + * The author may be reached at ecd@atecom.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. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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. + * + *******************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef CONFIG_ATM_IDT77252_USE_SUNI +#include "suni.h" +#endif /* CONFIG_ATM_IDT77252_USE_SUNI */ + + +#include "idt77252.h" +#include "idt77252_tables.h" + +static unsigned int vpibits = 1; + + +#define ATM_IDT77252_SEND_IDLE 1 + + +/* + * Debug HACKs. + */ +#define DEBUG_MODULE 1 +#undef HAVE_EEPROM /* does not work, yet. */ + +#ifdef CONFIG_ATM_IDT77252_DEBUG +static unsigned long debug = DBG_GENERAL; +#endif + + +#define SAR_RX_DELAY (SAR_CFG_RXINT_NODELAY) + + +/* + * SCQ Handling. + */ +static struct scq_info *alloc_scq(struct idt77252_dev *, int); +static void free_scq(struct idt77252_dev *, struct scq_info *); +static int queue_skb(struct idt77252_dev *, struct vc_map *, + struct sk_buff *, int oam); +static void drain_scq(struct idt77252_dev *, struct vc_map *); +static unsigned long get_free_scd(struct idt77252_dev *, struct vc_map *); +static void fill_scd(struct idt77252_dev *, struct scq_info *, int); + +/* + * FBQ Handling. + */ +static int push_rx_skb(struct idt77252_dev *, + struct sk_buff *, int queue); +static void recycle_rx_skb(struct idt77252_dev *, struct sk_buff *); +static void flush_rx_pool(struct idt77252_dev *, struct rx_pool *); +static void recycle_rx_pool_skb(struct idt77252_dev *, + struct rx_pool *); +static void add_rx_skb(struct idt77252_dev *, int queue, + unsigned int size, unsigned int count); + +/* + * RSQ Handling. + */ +static int init_rsq(struct idt77252_dev *); +static void deinit_rsq(struct idt77252_dev *); +static void idt77252_rx(struct idt77252_dev *); + +/* + * TSQ handling. + */ +static int init_tsq(struct idt77252_dev *); +static void deinit_tsq(struct idt77252_dev *); +static void idt77252_tx(struct idt77252_dev *); + + +/* + * ATM Interface. + */ +static void idt77252_dev_close(struct atm_dev *dev); +static int idt77252_open(struct atm_vcc *vcc); +static void idt77252_close(struct atm_vcc *vcc); +static int idt77252_send(struct atm_vcc *vcc, struct sk_buff *skb); +static int idt77252_send_oam(struct atm_vcc *vcc, void *cell, + int flags); +static void idt77252_phy_put(struct atm_dev *dev, unsigned char value, + unsigned long addr); +static unsigned char idt77252_phy_get(struct atm_dev *dev, unsigned long addr); +static int idt77252_change_qos(struct atm_vcc *vcc, struct atm_qos *qos, + int flags); +static int idt77252_proc_read(struct atm_dev *dev, loff_t * pos, + char *page); +static void idt77252_softint(struct work_struct *work); + + +static const struct atmdev_ops idt77252_ops = +{ + .dev_close = idt77252_dev_close, + .open = idt77252_open, + .close = idt77252_close, + .send = idt77252_send, + .send_oam = idt77252_send_oam, + .phy_put = idt77252_phy_put, + .phy_get = idt77252_phy_get, + .change_qos = idt77252_change_qos, + .proc_read = idt77252_proc_read, + .owner = THIS_MODULE +}; + +static struct idt77252_dev *idt77252_chain = NULL; +static unsigned int idt77252_sram_write_errors = 0; + +/*****************************************************************************/ +/* */ +/* I/O and Utility Bus */ +/* */ +/*****************************************************************************/ + +static void +waitfor_idle(struct idt77252_dev *card) +{ + u32 stat; + + stat = readl(SAR_REG_STAT); + while (stat & SAR_STAT_CMDBZ) + stat = readl(SAR_REG_STAT); +} + +static u32 +read_sram(struct idt77252_dev *card, unsigned long addr) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(SAR_CMD_READ_SRAM | (addr << 2), SAR_REG_CMD); + waitfor_idle(card); + value = readl(SAR_REG_DR0); + spin_unlock_irqrestore(&card->cmd_lock, flags); + return value; +} + +static void +write_sram(struct idt77252_dev *card, unsigned long addr, u32 value) +{ + unsigned long flags; + + if ((idt77252_sram_write_errors == 0) && + (((addr > card->tst[0] + card->tst_size - 2) && + (addr < card->tst[0] + card->tst_size)) || + ((addr > card->tst[1] + card->tst_size - 2) && + (addr < card->tst[1] + card->tst_size)))) { + printk("%s: ERROR: TST JMP section at %08lx written: %08x\n", + card->name, addr, value); + } + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(value, SAR_REG_DR0); + writel(SAR_CMD_WRITE_SRAM | (addr << 2), SAR_REG_CMD); + waitfor_idle(card); + spin_unlock_irqrestore(&card->cmd_lock, flags); +} + +static u8 +read_utility(void *dev, unsigned long ubus_addr) +{ + struct idt77252_dev *card = dev; + unsigned long flags; + u8 value; + + if (!card) { + printk("Error: No such device.\n"); + return -1; + } + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(SAR_CMD_READ_UTILITY + ubus_addr, SAR_REG_CMD); + waitfor_idle(card); + value = readl(SAR_REG_DR0); + spin_unlock_irqrestore(&card->cmd_lock, flags); + return value; +} + +static void +write_utility(void *dev, unsigned long ubus_addr, u8 value) +{ + struct idt77252_dev *card = dev; + unsigned long flags; + + if (!card) { + printk("Error: No such device.\n"); + return; + } + + spin_lock_irqsave(&card->cmd_lock, flags); + writel((u32) value, SAR_REG_DR0); + writel(SAR_CMD_WRITE_UTILITY + ubus_addr, SAR_REG_CMD); + waitfor_idle(card); + spin_unlock_irqrestore(&card->cmd_lock, flags); +} + +#ifdef HAVE_EEPROM +static u32 rdsrtab[] = +{ + SAR_GP_EECS | SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO, /* 1 */ + 0, + SAR_GP_EESCLK, /* 0 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO /* 1 */ +}; + +static u32 wrentab[] = +{ + SAR_GP_EECS | SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO, /* 1 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO, /* 1 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK /* 0 */ +}; + +static u32 rdtab[] = +{ + SAR_GP_EECS | SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO, /* 1 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO /* 1 */ +}; + +static u32 wrtab[] = +{ + SAR_GP_EECS | SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + 0, + SAR_GP_EESCLK, /* 0 */ + SAR_GP_EEDO, + SAR_GP_EESCLK | SAR_GP_EEDO, /* 1 */ + 0, + SAR_GP_EESCLK /* 0 */ +}; + +static u32 clktab[] = +{ + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0, + SAR_GP_EESCLK, + 0 +}; + +static u32 +idt77252_read_gp(struct idt77252_dev *card) +{ + u32 gp; + + gp = readl(SAR_REG_GP); +#if 0 + printk("RD: %s\n", gp & SAR_GP_EEDI ? "1" : "0"); +#endif + return gp; +} + +static void +idt77252_write_gp(struct idt77252_dev *card, u32 value) +{ + unsigned long flags; + +#if 0 + printk("WR: %s %s %s\n", value & SAR_GP_EECS ? " " : "/CS", + value & SAR_GP_EESCLK ? "HIGH" : "LOW ", + value & SAR_GP_EEDO ? "1" : "0"); +#endif + + spin_lock_irqsave(&card->cmd_lock, flags); + waitfor_idle(card); + writel(value, SAR_REG_GP); + spin_unlock_irqrestore(&card->cmd_lock, flags); +} + +static u8 +idt77252_eeprom_read_status(struct idt77252_dev *card) +{ + u8 byte; + u32 gp; + int i, j; + + gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO); + + for (i = 0; i < ARRAY_SIZE(rdsrtab); i++) { + idt77252_write_gp(card, gp | rdsrtab[i]); + udelay(5); + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + byte = 0; + for (i = 0, j = 0; i < 8; i++) { + byte <<= 1; + + idt77252_write_gp(card, gp | clktab[j++]); + udelay(5); + + byte |= idt77252_read_gp(card) & SAR_GP_EEDI ? 1 : 0; + + idt77252_write_gp(card, gp | clktab[j++]); + udelay(5); + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + return byte; +} + +static u8 +idt77252_eeprom_read_byte(struct idt77252_dev *card, u8 offset) +{ + u8 byte; + u32 gp; + int i, j; + + gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO); + + for (i = 0; i < ARRAY_SIZE(rdtab); i++) { + idt77252_write_gp(card, gp | rdtab[i]); + udelay(5); + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + for (i = 0, j = 0; i < 8; i++) { + idt77252_write_gp(card, gp | clktab[j++] | + (offset & 1 ? SAR_GP_EEDO : 0)); + udelay(5); + + idt77252_write_gp(card, gp | clktab[j++] | + (offset & 1 ? SAR_GP_EEDO : 0)); + udelay(5); + + offset >>= 1; + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + byte = 0; + for (i = 0, j = 0; i < 8; i++) { + byte <<= 1; + + idt77252_write_gp(card, gp | clktab[j++]); + udelay(5); + + byte |= idt77252_read_gp(card) & SAR_GP_EEDI ? 1 : 0; + + idt77252_write_gp(card, gp | clktab[j++]); + udelay(5); + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + return byte; +} + +static void +idt77252_eeprom_write_byte(struct idt77252_dev *card, u8 offset, u8 data) +{ + u32 gp; + int i, j; + + gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO); + + for (i = 0; i < ARRAY_SIZE(wrentab); i++) { + idt77252_write_gp(card, gp | wrentab[i]); + udelay(5); + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + for (i = 0; i < ARRAY_SIZE(wrtab); i++) { + idt77252_write_gp(card, gp | wrtab[i]); + udelay(5); + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + for (i = 0, j = 0; i < 8; i++) { + idt77252_write_gp(card, gp | clktab[j++] | + (offset & 1 ? SAR_GP_EEDO : 0)); + udelay(5); + + idt77252_write_gp(card, gp | clktab[j++] | + (offset & 1 ? SAR_GP_EEDO : 0)); + udelay(5); + + offset >>= 1; + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + + for (i = 0, j = 0; i < 8; i++) { + idt77252_write_gp(card, gp | clktab[j++] | + (data & 1 ? SAR_GP_EEDO : 0)); + udelay(5); + + idt77252_write_gp(card, gp | clktab[j++] | + (data & 1 ? SAR_GP_EEDO : 0)); + udelay(5); + + data >>= 1; + } + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); +} + +static void +idt77252_eeprom_init(struct idt77252_dev *card) +{ + u32 gp; + + gp = idt77252_read_gp(card) & ~(SAR_GP_EESCLK|SAR_GP_EECS|SAR_GP_EEDO); + + idt77252_write_gp(card, gp | SAR_GP_EECS | SAR_GP_EESCLK); + udelay(5); + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); + idt77252_write_gp(card, gp | SAR_GP_EECS | SAR_GP_EESCLK); + udelay(5); + idt77252_write_gp(card, gp | SAR_GP_EECS); + udelay(5); +} +#endif /* HAVE_EEPROM */ + + +#ifdef CONFIG_ATM_IDT77252_DEBUG +static void +dump_tct(struct idt77252_dev *card, int index) +{ + unsigned long tct; + int i; + + tct = (unsigned long) (card->tct_base + index * SAR_SRAM_TCT_SIZE); + + printk("%s: TCT %x:", card->name, index); + for (i = 0; i < 8; i++) { + printk(" %08x", read_sram(card, tct + i)); + } + printk("\n"); +} + +static void +idt77252_tx_dump(struct idt77252_dev *card) +{ + struct atm_vcc *vcc; + struct vc_map *vc; + int i; + + printk("%s\n", __func__); + for (i = 0; i < card->tct_size; i++) { + vc = card->vcs[i]; + if (!vc) + continue; + + vcc = NULL; + if (vc->rx_vcc) + vcc = vc->rx_vcc; + else if (vc->tx_vcc) + vcc = vc->tx_vcc; + + if (!vcc) + continue; + + printk("%s: Connection %d:\n", card->name, vc->index); + dump_tct(card, vc->index); + } +} +#endif + + +/*****************************************************************************/ +/* */ +/* SCQ Handling */ +/* */ +/*****************************************************************************/ + +static int +sb_pool_add(struct idt77252_dev *card, struct sk_buff *skb, int queue) +{ + struct sb_pool *pool = &card->sbpool[queue]; + int index; + + index = pool->index; + while (pool->skb[index]) { + index = (index + 1) & FBQ_MASK; + if (index == pool->index) + return -ENOBUFS; + } + + pool->skb[index] = skb; + IDT77252_PRV_POOL(skb) = POOL_HANDLE(queue, index); + + pool->index = (index + 1) & FBQ_MASK; + return 0; +} + +static void +sb_pool_remove(struct idt77252_dev *card, struct sk_buff *skb) +{ + unsigned int queue, index; + u32 handle; + + handle = IDT77252_PRV_POOL(skb); + + queue = POOL_QUEUE(handle); + if (queue > 3) + return; + + index = POOL_INDEX(handle); + if (index > FBQ_SIZE - 1) + return; + + card->sbpool[queue].skb[index] = NULL; +} + +static struct sk_buff * +sb_pool_skb(struct idt77252_dev *card, u32 handle) +{ + unsigned int queue, index; + + queue = POOL_QUEUE(handle); + if (queue > 3) + return NULL; + + index = POOL_INDEX(handle); + if (index > FBQ_SIZE - 1) + return NULL; + + return card->sbpool[queue].skb[index]; +} + +static struct scq_info * +alloc_scq(struct idt77252_dev *card, int class) +{ + struct scq_info *scq; + + scq = kzalloc(sizeof(struct scq_info), GFP_KERNEL); + if (!scq) + return NULL; + scq->base = dma_alloc_coherent(&card->pcidev->dev, SCQ_SIZE, + &scq->paddr, GFP_KERNEL); + if (scq->base == NULL) { + kfree(scq); + return NULL; + } + + scq->next = scq->base; + scq->last = scq->base + (SCQ_ENTRIES - 1); + atomic_set(&scq->used, 0); + + spin_lock_init(&scq->lock); + spin_lock_init(&scq->skblock); + + skb_queue_head_init(&scq->transmit); + skb_queue_head_init(&scq->pending); + + TXPRINTK("idt77252: SCQ: base 0x%p, next 0x%p, last 0x%p, paddr %08llx\n", + scq->base, scq->next, scq->last, (unsigned long long)scq->paddr); + + return scq; +} + +static void +free_scq(struct idt77252_dev *card, struct scq_info *scq) +{ + struct sk_buff *skb; + struct atm_vcc *vcc; + + dma_free_coherent(&card->pcidev->dev, SCQ_SIZE, + scq->base, scq->paddr); + + while ((skb = skb_dequeue(&scq->transmit))) { + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb->len, DMA_TO_DEVICE); + + vcc = ATM_SKB(skb)->vcc; + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb(skb); + } + + while ((skb = skb_dequeue(&scq->pending))) { + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb->len, DMA_TO_DEVICE); + + vcc = ATM_SKB(skb)->vcc; + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb(skb); + } + + kfree(scq); +} + + +static int +push_on_scq(struct idt77252_dev *card, struct vc_map *vc, struct sk_buff *skb) +{ + struct scq_info *scq = vc->scq; + unsigned long flags; + struct scqe *tbd; + int entries; + + TXPRINTK("%s: SCQ: next 0x%p\n", card->name, scq->next); + + atomic_inc(&scq->used); + entries = atomic_read(&scq->used); + if (entries > (SCQ_ENTRIES - 1)) { + atomic_dec(&scq->used); + goto out; + } + + skb_queue_tail(&scq->transmit, skb); + + spin_lock_irqsave(&vc->lock, flags); + if (vc->estimator) { + struct atm_vcc *vcc = vc->tx_vcc; + struct sock *sk = sk_atm(vcc); + + vc->estimator->cells += (skb->len + 47) / 48; + if (refcount_read(&sk->sk_wmem_alloc) > + (sk->sk_sndbuf >> 1)) { + u32 cps = vc->estimator->maxcps; + + vc->estimator->cps = cps; + vc->estimator->avcps = cps << 5; + if (vc->lacr < vc->init_er) { + vc->lacr = vc->init_er; + writel(TCMDQ_LACR | (vc->lacr << 16) | + vc->index, SAR_REG_TCMDQ); + } + } + } + spin_unlock_irqrestore(&vc->lock, flags); + + tbd = &IDT77252_PRV_TBD(skb); + + spin_lock_irqsave(&scq->lock, flags); + scq->next->word_1 = cpu_to_le32(tbd->word_1 | + SAR_TBD_TSIF | SAR_TBD_GTSI); + scq->next->word_2 = cpu_to_le32(tbd->word_2); + scq->next->word_3 = cpu_to_le32(tbd->word_3); + scq->next->word_4 = cpu_to_le32(tbd->word_4); + + if (scq->next == scq->last) + scq->next = scq->base; + else + scq->next++; + + write_sram(card, scq->scd, + scq->paddr + + (u32)((unsigned long)scq->next - (unsigned long)scq->base)); + spin_unlock_irqrestore(&scq->lock, flags); + + scq->trans_start = jiffies; + + if (test_and_clear_bit(VCF_IDLE, &vc->flags)) { + writel(TCMDQ_START_LACR | (vc->lacr << 16) | vc->index, + SAR_REG_TCMDQ); + } + + TXPRINTK("%d entries in SCQ used (push).\n", atomic_read(&scq->used)); + + XPRINTK("%s: SCQ (after push %2d) head = 0x%x, next = 0x%p.\n", + card->name, atomic_read(&scq->used), + read_sram(card, scq->scd + 1), scq->next); + + return 0; + +out: + if (time_after(jiffies, scq->trans_start + HZ)) { + printk("%s: Error pushing TBD for %d.%d\n", + card->name, vc->tx_vcc->vpi, vc->tx_vcc->vci); +#ifdef CONFIG_ATM_IDT77252_DEBUG + idt77252_tx_dump(card); +#endif + scq->trans_start = jiffies; + } + + return -ENOBUFS; +} + + +static void +drain_scq(struct idt77252_dev *card, struct vc_map *vc) +{ + struct scq_info *scq = vc->scq; + struct sk_buff *skb; + struct atm_vcc *vcc; + + TXPRINTK("%s: SCQ (before drain %2d) next = 0x%p.\n", + card->name, atomic_read(&scq->used), scq->next); + + skb = skb_dequeue(&scq->transmit); + if (skb) { + TXPRINTK("%s: freeing skb at %p.\n", card->name, skb); + + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb->len, DMA_TO_DEVICE); + + vcc = ATM_SKB(skb)->vcc; + + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb(skb); + + atomic_inc(&vcc->stats->tx); + } + + atomic_dec(&scq->used); + + spin_lock(&scq->skblock); + while ((skb = skb_dequeue(&scq->pending))) { + if (push_on_scq(card, vc, skb)) { + skb_queue_head(&vc->scq->pending, skb); + break; + } + } + spin_unlock(&scq->skblock); +} + +static int +queue_skb(struct idt77252_dev *card, struct vc_map *vc, + struct sk_buff *skb, int oam) +{ + struct atm_vcc *vcc; + struct scqe *tbd; + unsigned long flags; + int error; + int aal; + u32 word4; + + if (skb->len == 0) { + printk("%s: invalid skb->len (%d)\n", card->name, skb->len); + return -EINVAL; + } + + TXPRINTK("%s: Sending %d bytes of data.\n", + card->name, skb->len); + + tbd = &IDT77252_PRV_TBD(skb); + vcc = ATM_SKB(skb)->vcc; + word4 = (skb->data[0] << 24) | (skb->data[1] << 16) | + (skb->data[2] << 8) | (skb->data[3] << 0); + + IDT77252_PRV_PADDR(skb) = dma_map_single(&card->pcidev->dev, skb->data, + skb->len, DMA_TO_DEVICE); + + error = -EINVAL; + + if (oam) { + if (skb->len != 52) + goto errout; + + tbd->word_1 = SAR_TBD_OAM | ATM_CELL_PAYLOAD | SAR_TBD_EPDU; + tbd->word_2 = IDT77252_PRV_PADDR(skb) + 4; + tbd->word_3 = 0x00000000; + tbd->word_4 = word4; + + if (test_bit(VCF_RSV, &vc->flags)) + vc = card->vcs[0]; + + goto done; + } + + if (test_bit(VCF_RSV, &vc->flags)) { + printk("%s: Trying to transmit on reserved VC\n", card->name); + goto errout; + } + + aal = vcc->qos.aal; + + switch (aal) { + case ATM_AAL0: + case ATM_AAL34: + if (skb->len > 52) + goto errout; + + if (aal == ATM_AAL0) + tbd->word_1 = SAR_TBD_EPDU | SAR_TBD_AAL0 | + ATM_CELL_PAYLOAD; + else + tbd->word_1 = SAR_TBD_EPDU | SAR_TBD_AAL34 | + ATM_CELL_PAYLOAD; + + tbd->word_2 = IDT77252_PRV_PADDR(skb) + 4; + tbd->word_3 = 0x00000000; + tbd->word_4 = word4; + break; + + case ATM_AAL5: + tbd->word_1 = SAR_TBD_EPDU | SAR_TBD_AAL5 | skb->len; + tbd->word_2 = IDT77252_PRV_PADDR(skb); + tbd->word_3 = skb->len; + tbd->word_4 = (vcc->vpi << SAR_TBD_VPI_SHIFT) | + (vcc->vci << SAR_TBD_VCI_SHIFT); + break; + + case ATM_AAL1: + case ATM_AAL2: + default: + printk("%s: Traffic type not supported.\n", card->name); + error = -EPROTONOSUPPORT; + goto errout; + } + +done: + spin_lock_irqsave(&vc->scq->skblock, flags); + skb_queue_tail(&vc->scq->pending, skb); + + while ((skb = skb_dequeue(&vc->scq->pending))) { + if (push_on_scq(card, vc, skb)) { + skb_queue_head(&vc->scq->pending, skb); + break; + } + } + spin_unlock_irqrestore(&vc->scq->skblock, flags); + + return 0; + +errout: + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb->len, DMA_TO_DEVICE); + return error; +} + +static unsigned long +get_free_scd(struct idt77252_dev *card, struct vc_map *vc) +{ + int i; + + for (i = 0; i < card->scd_size; i++) { + if (!card->scd2vc[i]) { + card->scd2vc[i] = vc; + vc->scd_index = i; + return card->scd_base + i * SAR_SRAM_SCD_SIZE; + } + } + return 0; +} + +static void +fill_scd(struct idt77252_dev *card, struct scq_info *scq, int class) +{ + write_sram(card, scq->scd, scq->paddr); + write_sram(card, scq->scd + 1, 0x00000000); + write_sram(card, scq->scd + 2, 0xffffffff); + write_sram(card, scq->scd + 3, 0x00000000); +} + +static void +clear_scd(struct idt77252_dev *card, struct scq_info *scq, int class) +{ + return; +} + +/*****************************************************************************/ +/* */ +/* RSQ Handling */ +/* */ +/*****************************************************************************/ + +static int +init_rsq(struct idt77252_dev *card) +{ + struct rsq_entry *rsqe; + + card->rsq.base = dma_alloc_coherent(&card->pcidev->dev, RSQSIZE, + &card->rsq.paddr, GFP_KERNEL); + if (card->rsq.base == NULL) { + printk("%s: can't allocate RSQ.\n", card->name); + return -1; + } + + card->rsq.last = card->rsq.base + RSQ_NUM_ENTRIES - 1; + card->rsq.next = card->rsq.last; + for (rsqe = card->rsq.base; rsqe <= card->rsq.last; rsqe++) + rsqe->word_4 = 0; + + writel((unsigned long) card->rsq.last - (unsigned long) card->rsq.base, + SAR_REG_RSQH); + writel(card->rsq.paddr, SAR_REG_RSQB); + + IPRINTK("%s: RSQ base at 0x%lx (0x%x).\n", card->name, + (unsigned long) card->rsq.base, + readl(SAR_REG_RSQB)); + IPRINTK("%s: RSQ head = 0x%x, base = 0x%x, tail = 0x%x.\n", + card->name, + readl(SAR_REG_RSQH), + readl(SAR_REG_RSQB), + readl(SAR_REG_RSQT)); + + return 0; +} + +static void +deinit_rsq(struct idt77252_dev *card) +{ + dma_free_coherent(&card->pcidev->dev, RSQSIZE, + card->rsq.base, card->rsq.paddr); +} + +static void +dequeue_rx(struct idt77252_dev *card, struct rsq_entry *rsqe) +{ + struct atm_vcc *vcc; + struct sk_buff *skb; + struct rx_pool *rpp; + struct vc_map *vc; + u32 header, vpi, vci; + u32 stat; + int i; + + stat = le32_to_cpu(rsqe->word_4); + + if (stat & SAR_RSQE_IDLE) { + RXPRINTK("%s: message about inactive connection.\n", + card->name); + return; + } + + skb = sb_pool_skb(card, le32_to_cpu(rsqe->word_2)); + if (skb == NULL) { + printk("%s: NULL skb in %s, rsqe: %08x %08x %08x %08x\n", + card->name, __func__, + le32_to_cpu(rsqe->word_1), le32_to_cpu(rsqe->word_2), + le32_to_cpu(rsqe->word_3), le32_to_cpu(rsqe->word_4)); + return; + } + + header = le32_to_cpu(rsqe->word_1); + vpi = (header >> 16) & 0x00ff; + vci = (header >> 0) & 0xffff; + + RXPRINTK("%s: SDU for %d.%d received in buffer 0x%p (data 0x%p).\n", + card->name, vpi, vci, skb, skb->data); + + if ((vpi >= (1 << card->vpibits)) || (vci != (vci & card->vcimask))) { + printk("%s: SDU received for out-of-range vc %u.%u\n", + card->name, vpi, vci); + recycle_rx_skb(card, skb); + return; + } + + vc = card->vcs[VPCI2VC(card, vpi, vci)]; + if (!vc || !test_bit(VCF_RX, &vc->flags)) { + printk("%s: SDU received on non RX vc %u.%u\n", + card->name, vpi, vci); + recycle_rx_skb(card, skb); + return; + } + + vcc = vc->rx_vcc; + + dma_sync_single_for_cpu(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb_end_pointer(skb) - skb->data, + DMA_FROM_DEVICE); + + if ((vcc->qos.aal == ATM_AAL0) || + (vcc->qos.aal == ATM_AAL34)) { + struct sk_buff *sb; + unsigned char *cell; + u32 aal0; + + cell = skb->data; + for (i = (stat & SAR_RSQE_CELLCNT); i; i--) { + if ((sb = dev_alloc_skb(64)) == NULL) { + printk("%s: Can't allocate buffers for aal0.\n", + card->name); + atomic_add(i, &vcc->stats->rx_drop); + break; + } + if (!atm_charge(vcc, sb->truesize)) { + RXPRINTK("%s: atm_charge() dropped aal0 packets.\n", + card->name); + atomic_add(i - 1, &vcc->stats->rx_drop); + dev_kfree_skb(sb); + break; + } + aal0 = (vpi << ATM_HDR_VPI_SHIFT) | + (vci << ATM_HDR_VCI_SHIFT); + aal0 |= (stat & SAR_RSQE_EPDU) ? 0x00000002 : 0; + aal0 |= (stat & SAR_RSQE_CLP) ? 0x00000001 : 0; + + *((u32 *) sb->data) = aal0; + skb_put(sb, sizeof(u32)); + skb_put_data(sb, cell, ATM_CELL_PAYLOAD); + + ATM_SKB(sb)->vcc = vcc; + __net_timestamp(sb); + vcc->push(vcc, sb); + atomic_inc(&vcc->stats->rx); + + cell += ATM_CELL_PAYLOAD; + } + + recycle_rx_skb(card, skb); + return; + } + if (vcc->qos.aal != ATM_AAL5) { + printk("%s: Unexpected AAL type in dequeue_rx(): %d.\n", + card->name, vcc->qos.aal); + recycle_rx_skb(card, skb); + return; + } + skb->len = (stat & SAR_RSQE_CELLCNT) * ATM_CELL_PAYLOAD; + + rpp = &vc->rcv.rx_pool; + + __skb_queue_tail(&rpp->queue, skb); + rpp->len += skb->len; + + if (stat & SAR_RSQE_EPDU) { + unsigned char *l1l2; + unsigned int len; + + l1l2 = (unsigned char *) ((unsigned long) skb->data + skb->len - 6); + + len = (l1l2[0] << 8) | l1l2[1]; + len = len ? len : 0x10000; + + RXPRINTK("%s: PDU has %d bytes.\n", card->name, len); + + if ((len + 8 > rpp->len) || (len + (47 + 8) < rpp->len)) { + RXPRINTK("%s: AAL5 PDU size mismatch: %d != %d. " + "(CDC: %08x)\n", + card->name, len, rpp->len, readl(SAR_REG_CDC)); + recycle_rx_pool_skb(card, rpp); + atomic_inc(&vcc->stats->rx_err); + return; + } + if (stat & SAR_RSQE_CRC) { + RXPRINTK("%s: AAL5 CRC error.\n", card->name); + recycle_rx_pool_skb(card, rpp); + atomic_inc(&vcc->stats->rx_err); + return; + } + if (skb_queue_len(&rpp->queue) > 1) { + struct sk_buff *sb; + + skb = dev_alloc_skb(rpp->len); + if (!skb) { + RXPRINTK("%s: Can't alloc RX skb.\n", + card->name); + recycle_rx_pool_skb(card, rpp); + atomic_inc(&vcc->stats->rx_err); + return; + } + if (!atm_charge(vcc, skb->truesize)) { + recycle_rx_pool_skb(card, rpp); + dev_kfree_skb(skb); + return; + } + skb_queue_walk(&rpp->queue, sb) + skb_put_data(skb, sb->data, sb->len); + + recycle_rx_pool_skb(card, rpp); + + skb_trim(skb, len); + ATM_SKB(skb)->vcc = vcc; + __net_timestamp(skb); + + vcc->push(vcc, skb); + atomic_inc(&vcc->stats->rx); + + return; + } + + flush_rx_pool(card, rpp); + + if (!atm_charge(vcc, skb->truesize)) { + recycle_rx_skb(card, skb); + return; + } + + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb_end_pointer(skb) - skb->data, + DMA_FROM_DEVICE); + sb_pool_remove(card, skb); + + skb_trim(skb, len); + ATM_SKB(skb)->vcc = vcc; + __net_timestamp(skb); + + vcc->push(vcc, skb); + atomic_inc(&vcc->stats->rx); + + if (skb->truesize > SAR_FB_SIZE_3) + add_rx_skb(card, 3, SAR_FB_SIZE_3, 1); + else if (skb->truesize > SAR_FB_SIZE_2) + add_rx_skb(card, 2, SAR_FB_SIZE_2, 1); + else if (skb->truesize > SAR_FB_SIZE_1) + add_rx_skb(card, 1, SAR_FB_SIZE_1, 1); + else + add_rx_skb(card, 0, SAR_FB_SIZE_0, 1); + return; + } +} + +static void +idt77252_rx(struct idt77252_dev *card) +{ + struct rsq_entry *rsqe; + + if (card->rsq.next == card->rsq.last) + rsqe = card->rsq.base; + else + rsqe = card->rsq.next + 1; + + if (!(le32_to_cpu(rsqe->word_4) & SAR_RSQE_VALID)) { + RXPRINTK("%s: no entry in RSQ.\n", card->name); + return; + } + + do { + dequeue_rx(card, rsqe); + rsqe->word_4 = 0; + card->rsq.next = rsqe; + if (card->rsq.next == card->rsq.last) + rsqe = card->rsq.base; + else + rsqe = card->rsq.next + 1; + } while (le32_to_cpu(rsqe->word_4) & SAR_RSQE_VALID); + + writel((unsigned long) card->rsq.next - (unsigned long) card->rsq.base, + SAR_REG_RSQH); +} + +static void +idt77252_rx_raw(struct idt77252_dev *card) +{ + struct sk_buff *queue; + u32 head, tail; + struct atm_vcc *vcc; + struct vc_map *vc; + struct sk_buff *sb; + + if (card->raw_cell_head == NULL) { + u32 handle = le32_to_cpu(*(card->raw_cell_hnd + 1)); + card->raw_cell_head = sb_pool_skb(card, handle); + } + + queue = card->raw_cell_head; + if (!queue) + return; + + head = IDT77252_PRV_PADDR(queue) + (queue->data - queue->head - 16); + tail = readl(SAR_REG_RAWCT); + + dma_sync_single_for_cpu(&card->pcidev->dev, IDT77252_PRV_PADDR(queue), + skb_end_offset(queue) - 16, + DMA_FROM_DEVICE); + + while (head != tail) { + unsigned int vpi, vci; + u32 header; + + header = le32_to_cpu(*(u32 *) &queue->data[0]); + + vpi = (header & ATM_HDR_VPI_MASK) >> ATM_HDR_VPI_SHIFT; + vci = (header & ATM_HDR_VCI_MASK) >> ATM_HDR_VCI_SHIFT; + +#ifdef CONFIG_ATM_IDT77252_DEBUG + if (debug & DBG_RAW_CELL) { + int i; + + printk("%s: raw cell %x.%02x.%04x.%x.%x\n", + card->name, (header >> 28) & 0x000f, + (header >> 20) & 0x00ff, + (header >> 4) & 0xffff, + (header >> 1) & 0x0007, + (header >> 0) & 0x0001); + for (i = 16; i < 64; i++) + printk(" %02x", queue->data[i]); + printk("\n"); + } +#endif + + if (vpi >= (1<vpibits) || vci >= (1<vcibits)) { + RPRINTK("%s: SDU received for out-of-range vc %u.%u\n", + card->name, vpi, vci); + goto drop; + } + + vc = card->vcs[VPCI2VC(card, vpi, vci)]; + if (!vc || !test_bit(VCF_RX, &vc->flags)) { + RPRINTK("%s: SDU received on non RX vc %u.%u\n", + card->name, vpi, vci); + goto drop; + } + + vcc = vc->rx_vcc; + + if (vcc->qos.aal != ATM_AAL0) { + RPRINTK("%s: raw cell for non AAL0 vc %u.%u\n", + card->name, vpi, vci); + atomic_inc(&vcc->stats->rx_drop); + goto drop; + } + + if ((sb = dev_alloc_skb(64)) == NULL) { + printk("%s: Can't allocate buffers for AAL0.\n", + card->name); + atomic_inc(&vcc->stats->rx_err); + goto drop; + } + + if (!atm_charge(vcc, sb->truesize)) { + RXPRINTK("%s: atm_charge() dropped AAL0 packets.\n", + card->name); + dev_kfree_skb(sb); + goto drop; + } + + *((u32 *) sb->data) = header; + skb_put(sb, sizeof(u32)); + skb_put_data(sb, &(queue->data[16]), ATM_CELL_PAYLOAD); + + ATM_SKB(sb)->vcc = vcc; + __net_timestamp(sb); + vcc->push(vcc, sb); + atomic_inc(&vcc->stats->rx); + +drop: + skb_pull(queue, 64); + + head = IDT77252_PRV_PADDR(queue) + + (queue->data - queue->head - 16); + + if (queue->len < 128) { + struct sk_buff *next; + u32 handle; + + head = le32_to_cpu(*(u32 *) &queue->data[0]); + handle = le32_to_cpu(*(u32 *) &queue->data[4]); + + next = sb_pool_skb(card, handle); + recycle_rx_skb(card, queue); + + if (next) { + card->raw_cell_head = next; + queue = card->raw_cell_head; + dma_sync_single_for_cpu(&card->pcidev->dev, + IDT77252_PRV_PADDR(queue), + (skb_end_pointer(queue) - + queue->data), + DMA_FROM_DEVICE); + } else { + card->raw_cell_head = NULL; + printk("%s: raw cell queue overrun\n", + card->name); + break; + } + } + } +} + + +/*****************************************************************************/ +/* */ +/* TSQ Handling */ +/* */ +/*****************************************************************************/ + +static int +init_tsq(struct idt77252_dev *card) +{ + struct tsq_entry *tsqe; + + card->tsq.base = dma_alloc_coherent(&card->pcidev->dev, RSQSIZE, + &card->tsq.paddr, GFP_KERNEL); + if (card->tsq.base == NULL) { + printk("%s: can't allocate TSQ.\n", card->name); + return -1; + } + + card->tsq.last = card->tsq.base + TSQ_NUM_ENTRIES - 1; + card->tsq.next = card->tsq.last; + for (tsqe = card->tsq.base; tsqe <= card->tsq.last; tsqe++) + tsqe->word_2 = cpu_to_le32(SAR_TSQE_INVALID); + + writel(card->tsq.paddr, SAR_REG_TSQB); + writel((unsigned long) card->tsq.next - (unsigned long) card->tsq.base, + SAR_REG_TSQH); + + return 0; +} + +static void +deinit_tsq(struct idt77252_dev *card) +{ + dma_free_coherent(&card->pcidev->dev, TSQSIZE, + card->tsq.base, card->tsq.paddr); +} + +static void +idt77252_tx(struct idt77252_dev *card) +{ + struct tsq_entry *tsqe; + unsigned int vpi, vci; + struct vc_map *vc; + u32 conn, stat; + + if (card->tsq.next == card->tsq.last) + tsqe = card->tsq.base; + else + tsqe = card->tsq.next + 1; + + TXPRINTK("idt77252_tx: tsq %p: base %p, next %p, last %p\n", tsqe, + card->tsq.base, card->tsq.next, card->tsq.last); + TXPRINTK("idt77252_tx: tsqb %08x, tsqt %08x, tsqh %08x, \n", + readl(SAR_REG_TSQB), + readl(SAR_REG_TSQT), + readl(SAR_REG_TSQH)); + + stat = le32_to_cpu(tsqe->word_2); + + if (stat & SAR_TSQE_INVALID) + return; + + do { + TXPRINTK("tsqe: 0x%p [0x%08x 0x%08x]\n", tsqe, + le32_to_cpu(tsqe->word_1), + le32_to_cpu(tsqe->word_2)); + + switch (stat & SAR_TSQE_TYPE) { + case SAR_TSQE_TYPE_TIMER: + TXPRINTK("%s: Timer RollOver detected.\n", card->name); + break; + + case SAR_TSQE_TYPE_IDLE: + + conn = le32_to_cpu(tsqe->word_1); + + if (SAR_TSQE_TAG(stat) == 0x10) { +#ifdef NOTDEF + printk("%s: Connection %d halted.\n", + card->name, + le32_to_cpu(tsqe->word_1) & 0x1fff); +#endif + break; + } + + vc = card->vcs[conn & 0x1fff]; + if (!vc) { + printk("%s: could not find VC from conn %d\n", + card->name, conn & 0x1fff); + break; + } + + printk("%s: Connection %d IDLE.\n", + card->name, vc->index); + + set_bit(VCF_IDLE, &vc->flags); + break; + + case SAR_TSQE_TYPE_TSR: + + conn = le32_to_cpu(tsqe->word_1); + + vc = card->vcs[conn & 0x1fff]; + if (!vc) { + printk("%s: no VC at index %d\n", + card->name, + le32_to_cpu(tsqe->word_1) & 0x1fff); + break; + } + + drain_scq(card, vc); + break; + + case SAR_TSQE_TYPE_TBD_COMP: + + conn = le32_to_cpu(tsqe->word_1); + + vpi = (conn >> SAR_TBD_VPI_SHIFT) & 0x00ff; + vci = (conn >> SAR_TBD_VCI_SHIFT) & 0xffff; + + if (vpi >= (1 << card->vpibits) || + vci >= (1 << card->vcibits)) { + printk("%s: TBD complete: " + "out of range VPI.VCI %u.%u\n", + card->name, vpi, vci); + break; + } + + vc = card->vcs[VPCI2VC(card, vpi, vci)]; + if (!vc) { + printk("%s: TBD complete: " + "no VC at VPI.VCI %u.%u\n", + card->name, vpi, vci); + break; + } + + drain_scq(card, vc); + break; + } + + tsqe->word_2 = cpu_to_le32(SAR_TSQE_INVALID); + + card->tsq.next = tsqe; + if (card->tsq.next == card->tsq.last) + tsqe = card->tsq.base; + else + tsqe = card->tsq.next + 1; + + TXPRINTK("tsqe: %p: base %p, next %p, last %p\n", tsqe, + card->tsq.base, card->tsq.next, card->tsq.last); + + stat = le32_to_cpu(tsqe->word_2); + + } while (!(stat & SAR_TSQE_INVALID)); + + writel((unsigned long)card->tsq.next - (unsigned long)card->tsq.base, + SAR_REG_TSQH); + + XPRINTK("idt77252_tx-after writel%d: TSQ head = 0x%x, tail = 0x%x, next = 0x%p.\n", + card->index, readl(SAR_REG_TSQH), + readl(SAR_REG_TSQT), card->tsq.next); +} + + +static void +tst_timer(struct timer_list *t) +{ + struct idt77252_dev *card = from_timer(card, t, tst_timer); + unsigned long base, idle, jump; + unsigned long flags; + u32 pc; + int e; + + spin_lock_irqsave(&card->tst_lock, flags); + + base = card->tst[card->tst_index]; + idle = card->tst[card->tst_index ^ 1]; + + if (test_bit(TST_SWITCH_WAIT, &card->tst_state)) { + jump = base + card->tst_size - 2; + + pc = readl(SAR_REG_NOW) >> 2; + if ((pc ^ idle) & ~(card->tst_size - 1)) { + mod_timer(&card->tst_timer, jiffies + 1); + goto out; + } + + clear_bit(TST_SWITCH_WAIT, &card->tst_state); + + card->tst_index ^= 1; + write_sram(card, jump, TSTE_OPC_JMP | (base << 2)); + + base = card->tst[card->tst_index]; + idle = card->tst[card->tst_index ^ 1]; + + for (e = 0; e < card->tst_size - 2; e++) { + if (card->soft_tst[e].tste & TSTE_PUSH_IDLE) { + write_sram(card, idle + e, + card->soft_tst[e].tste & TSTE_MASK); + card->soft_tst[e].tste &= ~(TSTE_PUSH_IDLE); + } + } + } + + if (test_and_clear_bit(TST_SWITCH_PENDING, &card->tst_state)) { + + for (e = 0; e < card->tst_size - 2; e++) { + if (card->soft_tst[e].tste & TSTE_PUSH_ACTIVE) { + write_sram(card, idle + e, + card->soft_tst[e].tste & TSTE_MASK); + card->soft_tst[e].tste &= ~(TSTE_PUSH_ACTIVE); + card->soft_tst[e].tste |= TSTE_PUSH_IDLE; + } + } + + jump = base + card->tst_size - 2; + + write_sram(card, jump, TSTE_OPC_NULL); + set_bit(TST_SWITCH_WAIT, &card->tst_state); + + mod_timer(&card->tst_timer, jiffies + 1); + } + +out: + spin_unlock_irqrestore(&card->tst_lock, flags); +} + +static int +__fill_tst(struct idt77252_dev *card, struct vc_map *vc, + int n, unsigned int opc) +{ + unsigned long cl, avail; + unsigned long idle; + int e, r; + u32 data; + + avail = card->tst_size - 2; + for (e = 0; e < avail; e++) { + if (card->soft_tst[e].vc == NULL) + break; + } + if (e >= avail) { + printk("%s: No free TST entries found\n", card->name); + return -1; + } + + NPRINTK("%s: conn %d: first TST entry at %d.\n", + card->name, vc ? vc->index : -1, e); + + r = n; + cl = avail; + data = opc & TSTE_OPC_MASK; + if (vc && (opc != TSTE_OPC_NULL)) + data = opc | vc->index; + + idle = card->tst[card->tst_index ^ 1]; + + /* + * Fill Soft TST. + */ + while (r > 0) { + if ((cl >= avail) && (card->soft_tst[e].vc == NULL)) { + if (vc) + card->soft_tst[e].vc = vc; + else + card->soft_tst[e].vc = (void *)-1; + + card->soft_tst[e].tste = data; + if (timer_pending(&card->tst_timer)) + card->soft_tst[e].tste |= TSTE_PUSH_ACTIVE; + else { + write_sram(card, idle + e, data); + card->soft_tst[e].tste |= TSTE_PUSH_IDLE; + } + + cl -= card->tst_size; + r--; + } + + if (++e == avail) + e = 0; + cl += n; + } + + return 0; +} + +static int +fill_tst(struct idt77252_dev *card, struct vc_map *vc, int n, unsigned int opc) +{ + unsigned long flags; + int res; + + spin_lock_irqsave(&card->tst_lock, flags); + + res = __fill_tst(card, vc, n, opc); + + set_bit(TST_SWITCH_PENDING, &card->tst_state); + if (!timer_pending(&card->tst_timer)) + mod_timer(&card->tst_timer, jiffies + 1); + + spin_unlock_irqrestore(&card->tst_lock, flags); + return res; +} + +static int +__clear_tst(struct idt77252_dev *card, struct vc_map *vc) +{ + unsigned long idle; + int e; + + idle = card->tst[card->tst_index ^ 1]; + + for (e = 0; e < card->tst_size - 2; e++) { + if (card->soft_tst[e].vc == vc) { + card->soft_tst[e].vc = NULL; + + card->soft_tst[e].tste = TSTE_OPC_VAR; + if (timer_pending(&card->tst_timer)) + card->soft_tst[e].tste |= TSTE_PUSH_ACTIVE; + else { + write_sram(card, idle + e, TSTE_OPC_VAR); + card->soft_tst[e].tste |= TSTE_PUSH_IDLE; + } + } + } + + return 0; +} + +static int +clear_tst(struct idt77252_dev *card, struct vc_map *vc) +{ + unsigned long flags; + int res; + + spin_lock_irqsave(&card->tst_lock, flags); + + res = __clear_tst(card, vc); + + set_bit(TST_SWITCH_PENDING, &card->tst_state); + if (!timer_pending(&card->tst_timer)) + mod_timer(&card->tst_timer, jiffies + 1); + + spin_unlock_irqrestore(&card->tst_lock, flags); + return res; +} + +static int +change_tst(struct idt77252_dev *card, struct vc_map *vc, + int n, unsigned int opc) +{ + unsigned long flags; + int res; + + spin_lock_irqsave(&card->tst_lock, flags); + + __clear_tst(card, vc); + res = __fill_tst(card, vc, n, opc); + + set_bit(TST_SWITCH_PENDING, &card->tst_state); + if (!timer_pending(&card->tst_timer)) + mod_timer(&card->tst_timer, jiffies + 1); + + spin_unlock_irqrestore(&card->tst_lock, flags); + return res; +} + + +static int +set_tct(struct idt77252_dev *card, struct vc_map *vc) +{ + unsigned long tct; + + tct = (unsigned long) (card->tct_base + vc->index * SAR_SRAM_TCT_SIZE); + + switch (vc->class) { + case SCHED_CBR: + OPRINTK("%s: writing TCT at 0x%lx, SCD 0x%lx.\n", + card->name, tct, vc->scq->scd); + + write_sram(card, tct + 0, TCT_CBR | vc->scq->scd); + write_sram(card, tct + 1, 0); + write_sram(card, tct + 2, 0); + write_sram(card, tct + 3, 0); + write_sram(card, tct + 4, 0); + write_sram(card, tct + 5, 0); + write_sram(card, tct + 6, 0); + write_sram(card, tct + 7, 0); + break; + + case SCHED_UBR: + OPRINTK("%s: writing TCT at 0x%lx, SCD 0x%lx.\n", + card->name, tct, vc->scq->scd); + + write_sram(card, tct + 0, TCT_UBR | vc->scq->scd); + write_sram(card, tct + 1, 0); + write_sram(card, tct + 2, TCT_TSIF); + write_sram(card, tct + 3, TCT_HALT | TCT_IDLE); + write_sram(card, tct + 4, 0); + write_sram(card, tct + 5, vc->init_er); + write_sram(card, tct + 6, 0); + write_sram(card, tct + 7, TCT_FLAG_UBR); + break; + + case SCHED_VBR: + case SCHED_ABR: + default: + return -ENOSYS; + } + + return 0; +} + +/*****************************************************************************/ +/* */ +/* FBQ Handling */ +/* */ +/*****************************************************************************/ + +static __inline__ int +idt77252_fbq_level(struct idt77252_dev *card, int queue) +{ + return (readl(SAR_REG_STAT) >> (16 + (queue << 2))) & 0x0f; +} + +static __inline__ int +idt77252_fbq_full(struct idt77252_dev *card, int queue) +{ + return (readl(SAR_REG_STAT) >> (16 + (queue << 2))) == 0x0f; +} + +static int +push_rx_skb(struct idt77252_dev *card, struct sk_buff *skb, int queue) +{ + unsigned long flags; + u32 handle; + u32 addr; + + skb->data = skb->head; + skb_reset_tail_pointer(skb); + skb->len = 0; + + skb_reserve(skb, 16); + + switch (queue) { + case 0: + skb_put(skb, SAR_FB_SIZE_0); + break; + case 1: + skb_put(skb, SAR_FB_SIZE_1); + break; + case 2: + skb_put(skb, SAR_FB_SIZE_2); + break; + case 3: + skb_put(skb, SAR_FB_SIZE_3); + break; + default: + return -1; + } + + if (idt77252_fbq_full(card, queue)) + return -1; + + memset(&skb->data[(skb->len & ~(0x3f)) - 64], 0, 2 * sizeof(u32)); + + handle = IDT77252_PRV_POOL(skb); + addr = IDT77252_PRV_PADDR(skb); + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(handle, card->fbq[queue]); + writel(addr, card->fbq[queue]); + spin_unlock_irqrestore(&card->cmd_lock, flags); + + return 0; +} + +static void +add_rx_skb(struct idt77252_dev *card, int queue, + unsigned int size, unsigned int count) +{ + struct sk_buff *skb; + dma_addr_t paddr; + u32 handle; + + while (count--) { + skb = dev_alloc_skb(size); + if (!skb) + return; + + if (sb_pool_add(card, skb, queue)) { + printk("%s: SB POOL full\n", __func__); + goto outfree; + } + + paddr = dma_map_single(&card->pcidev->dev, skb->data, + skb_end_pointer(skb) - skb->data, + DMA_FROM_DEVICE); + IDT77252_PRV_PADDR(skb) = paddr; + + if (push_rx_skb(card, skb, queue)) { + printk("%s: FB QUEUE full\n", __func__); + goto outunmap; + } + } + + return; + +outunmap: + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb_end_pointer(skb) - skb->data, DMA_FROM_DEVICE); + + handle = IDT77252_PRV_POOL(skb); + card->sbpool[POOL_QUEUE(handle)].skb[POOL_INDEX(handle)] = NULL; + +outfree: + dev_kfree_skb(skb); +} + + +static void +recycle_rx_skb(struct idt77252_dev *card, struct sk_buff *skb) +{ + u32 handle = IDT77252_PRV_POOL(skb); + int err; + + dma_sync_single_for_device(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb_end_pointer(skb) - skb->data, + DMA_FROM_DEVICE); + + err = push_rx_skb(card, skb, POOL_QUEUE(handle)); + if (err) { + dma_unmap_single(&card->pcidev->dev, IDT77252_PRV_PADDR(skb), + skb_end_pointer(skb) - skb->data, + DMA_FROM_DEVICE); + sb_pool_remove(card, skb); + dev_kfree_skb(skb); + } +} + +static void +flush_rx_pool(struct idt77252_dev *card, struct rx_pool *rpp) +{ + skb_queue_head_init(&rpp->queue); + rpp->len = 0; +} + +static void +recycle_rx_pool_skb(struct idt77252_dev *card, struct rx_pool *rpp) +{ + struct sk_buff *skb, *tmp; + + skb_queue_walk_safe(&rpp->queue, skb, tmp) + recycle_rx_skb(card, skb); + + flush_rx_pool(card, rpp); +} + +/*****************************************************************************/ +/* */ +/* ATM Interface */ +/* */ +/*****************************************************************************/ + +static void +idt77252_phy_put(struct atm_dev *dev, unsigned char value, unsigned long addr) +{ + write_utility(dev->dev_data, 0x100 + (addr & 0x1ff), value); +} + +static unsigned char +idt77252_phy_get(struct atm_dev *dev, unsigned long addr) +{ + return read_utility(dev->dev_data, 0x100 + (addr & 0x1ff)); +} + +static inline int +idt77252_send_skb(struct atm_vcc *vcc, struct sk_buff *skb, int oam) +{ + struct atm_dev *dev = vcc->dev; + struct idt77252_dev *card = dev->dev_data; + struct vc_map *vc = vcc->dev_data; + int err; + + if (vc == NULL) { + printk("%s: NULL connection in send().\n", card->name); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb(skb); + return -EINVAL; + } + if (!test_bit(VCF_TX, &vc->flags)) { + printk("%s: Trying to transmit on a non-tx VC.\n", card->name); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb(skb); + return -EINVAL; + } + + switch (vcc->qos.aal) { + case ATM_AAL0: + case ATM_AAL1: + case ATM_AAL5: + break; + default: + printk("%s: Unsupported AAL: %d\n", card->name, vcc->qos.aal); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb(skb); + return -EINVAL; + } + + if (skb_shinfo(skb)->nr_frags != 0) { + printk("%s: No scatter-gather yet.\n", card->name); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb(skb); + return -EINVAL; + } + ATM_SKB(skb)->vcc = vcc; + + err = queue_skb(card, vc, skb, oam); + if (err) { + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb(skb); + return err; + } + + return 0; +} + +static int idt77252_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + return idt77252_send_skb(vcc, skb, 0); +} + +static int +idt77252_send_oam(struct atm_vcc *vcc, void *cell, int flags) +{ + struct atm_dev *dev = vcc->dev; + struct idt77252_dev *card = dev->dev_data; + struct sk_buff *skb; + + skb = dev_alloc_skb(64); + if (!skb) { + printk("%s: Out of memory in send_oam().\n", card->name); + atomic_inc(&vcc->stats->tx_err); + return -ENOMEM; + } + refcount_add(skb->truesize, &sk_atm(vcc)->sk_wmem_alloc); + + skb_put_data(skb, cell, 52); + + return idt77252_send_skb(vcc, skb, 1); +} + +static __inline__ unsigned int +idt77252_fls(unsigned int x) +{ + int r = 1; + + if (x == 0) + return 0; + if (x & 0xffff0000) { + x >>= 16; + r += 16; + } + if (x & 0xff00) { + x >>= 8; + r += 8; + } + if (x & 0xf0) { + x >>= 4; + r += 4; + } + if (x & 0xc) { + x >>= 2; + r += 2; + } + if (x & 0x2) + r += 1; + return r; +} + +static u16 +idt77252_int_to_atmfp(unsigned int rate) +{ + u16 m, e; + + if (rate == 0) + return 0; + e = idt77252_fls(rate) - 1; + if (e < 9) + m = (rate - (1 << e)) << (9 - e); + else if (e == 9) + m = (rate - (1 << e)); + else /* e > 9 */ + m = (rate - (1 << e)) >> (e - 9); + return 0x4000 | (e << 9) | m; +} + +static u8 +idt77252_rate_logindex(struct idt77252_dev *card, int pcr) +{ + u16 afp; + + afp = idt77252_int_to_atmfp(pcr < 0 ? -pcr : pcr); + if (pcr < 0) + return rate_to_log[(afp >> 5) & 0x1ff]; + return rate_to_log[((afp >> 5) + 1) & 0x1ff]; +} + +static void +idt77252_est_timer(struct timer_list *t) +{ + struct rate_estimator *est = from_timer(est, t, timer); + struct vc_map *vc = est->vc; + struct idt77252_dev *card = vc->card; + unsigned long flags; + u32 rate, cps; + u64 ncells; + u8 lacr; + + spin_lock_irqsave(&vc->lock, flags); + if (!vc->estimator) + goto out; + ncells = est->cells; + + rate = ((u32)(ncells - est->last_cells)) << (7 - est->interval); + est->last_cells = ncells; + est->avcps += ((long)rate - (long)est->avcps) >> est->ewma_log; + est->cps = (est->avcps + 0x1f) >> 5; + + cps = est->cps; + if (cps < (est->maxcps >> 4)) + cps = est->maxcps >> 4; + + lacr = idt77252_rate_logindex(card, cps); + if (lacr > vc->max_er) + lacr = vc->max_er; + + if (lacr != vc->lacr) { + vc->lacr = lacr; + writel(TCMDQ_LACR|(vc->lacr << 16)|vc->index, SAR_REG_TCMDQ); + } + + est->timer.expires = jiffies + ((HZ / 4) << est->interval); + add_timer(&est->timer); + +out: + spin_unlock_irqrestore(&vc->lock, flags); +} + +static struct rate_estimator * +idt77252_init_est(struct vc_map *vc, int pcr) +{ + struct rate_estimator *est; + + est = kzalloc(sizeof(struct rate_estimator), GFP_KERNEL); + if (!est) + return NULL; + est->maxcps = pcr < 0 ? -pcr : pcr; + est->cps = est->maxcps; + est->avcps = est->cps << 5; + est->vc = vc; + + est->interval = 2; /* XXX: make this configurable */ + est->ewma_log = 2; /* XXX: make this configurable */ + timer_setup(&est->timer, idt77252_est_timer, 0); + mod_timer(&est->timer, jiffies + ((HZ / 4) << est->interval)); + + return est; +} + +static int +idt77252_init_cbr(struct idt77252_dev *card, struct vc_map *vc, + struct atm_vcc *vcc, struct atm_qos *qos) +{ + int tst_free, tst_used, tst_entries; + unsigned long tmpl, modl; + int tcr, tcra; + + if ((qos->txtp.max_pcr == 0) && + (qos->txtp.pcr == 0) && (qos->txtp.min_pcr == 0)) { + printk("%s: trying to open a CBR VC with cell rate = 0\n", + card->name); + return -EINVAL; + } + + tst_used = 0; + tst_free = card->tst_free; + if (test_bit(VCF_TX, &vc->flags)) + tst_used = vc->ntste; + tst_free += tst_used; + + tcr = atm_pcr_goal(&qos->txtp); + tcra = tcr >= 0 ? tcr : -tcr; + + TXPRINTK("%s: CBR target cell rate = %d\n", card->name, tcra); + + tmpl = (unsigned long) tcra * ((unsigned long) card->tst_size - 2); + modl = tmpl % (unsigned long)card->utopia_pcr; + + tst_entries = (int) (tmpl / card->utopia_pcr); + if (tcr > 0) { + if (modl > 0) + tst_entries++; + } else if (tcr == 0) { + tst_entries = tst_free - SAR_TST_RESERVED; + if (tst_entries <= 0) { + printk("%s: no CBR bandwidth free.\n", card->name); + return -ENOSR; + } + } + + if (tst_entries == 0) { + printk("%s: selected CBR bandwidth < granularity.\n", + card->name); + return -EINVAL; + } + + if (tst_entries > (tst_free - SAR_TST_RESERVED)) { + printk("%s: not enough CBR bandwidth free.\n", card->name); + return -ENOSR; + } + + vc->ntste = tst_entries; + + card->tst_free = tst_free - tst_entries; + if (test_bit(VCF_TX, &vc->flags)) { + if (tst_used == tst_entries) + return 0; + + OPRINTK("%s: modify %d -> %d entries in TST.\n", + card->name, tst_used, tst_entries); + change_tst(card, vc, tst_entries, TSTE_OPC_CBR); + return 0; + } + + OPRINTK("%s: setting %d entries in TST.\n", card->name, tst_entries); + fill_tst(card, vc, tst_entries, TSTE_OPC_CBR); + return 0; +} + +static int +idt77252_init_ubr(struct idt77252_dev *card, struct vc_map *vc, + struct atm_vcc *vcc, struct atm_qos *qos) +{ + struct rate_estimator *est = NULL; + unsigned long flags; + int tcr; + + spin_lock_irqsave(&vc->lock, flags); + if (vc->estimator) { + est = vc->estimator; + vc->estimator = NULL; + } + spin_unlock_irqrestore(&vc->lock, flags); + if (est) { + del_timer_sync(&est->timer); + kfree(est); + } + + tcr = atm_pcr_goal(&qos->txtp); + if (tcr == 0) + tcr = card->link_pcr; + + vc->estimator = idt77252_init_est(vc, tcr); + + vc->class = SCHED_UBR; + vc->init_er = idt77252_rate_logindex(card, tcr); + vc->lacr = vc->init_er; + if (tcr < 0) + vc->max_er = vc->init_er; + else + vc->max_er = 0xff; + + return 0; +} + +static int +idt77252_init_tx(struct idt77252_dev *card, struct vc_map *vc, + struct atm_vcc *vcc, struct atm_qos *qos) +{ + int error; + + if (test_bit(VCF_TX, &vc->flags)) + return -EBUSY; + + switch (qos->txtp.traffic_class) { + case ATM_CBR: + vc->class = SCHED_CBR; + break; + + case ATM_UBR: + vc->class = SCHED_UBR; + break; + + case ATM_VBR: + case ATM_ABR: + default: + return -EPROTONOSUPPORT; + } + + vc->scq = alloc_scq(card, vc->class); + if (!vc->scq) { + printk("%s: can't get SCQ.\n", card->name); + return -ENOMEM; + } + + vc->scq->scd = get_free_scd(card, vc); + if (vc->scq->scd == 0) { + printk("%s: no SCD available.\n", card->name); + free_scq(card, vc->scq); + return -ENOMEM; + } + + fill_scd(card, vc->scq, vc->class); + + if (set_tct(card, vc)) { + printk("%s: class %d not supported.\n", + card->name, qos->txtp.traffic_class); + + card->scd2vc[vc->scd_index] = NULL; + free_scq(card, vc->scq); + return -EPROTONOSUPPORT; + } + + switch (vc->class) { + case SCHED_CBR: + error = idt77252_init_cbr(card, vc, vcc, qos); + if (error) { + card->scd2vc[vc->scd_index] = NULL; + free_scq(card, vc->scq); + return error; + } + + clear_bit(VCF_IDLE, &vc->flags); + writel(TCMDQ_START | vc->index, SAR_REG_TCMDQ); + break; + + case SCHED_UBR: + error = idt77252_init_ubr(card, vc, vcc, qos); + if (error) { + card->scd2vc[vc->scd_index] = NULL; + free_scq(card, vc->scq); + return error; + } + + set_bit(VCF_IDLE, &vc->flags); + break; + } + + vc->tx_vcc = vcc; + set_bit(VCF_TX, &vc->flags); + return 0; +} + +static int +idt77252_init_rx(struct idt77252_dev *card, struct vc_map *vc, + struct atm_vcc *vcc, struct atm_qos *qos) +{ + unsigned long flags; + unsigned long addr; + u32 rcte = 0; + + if (test_bit(VCF_RX, &vc->flags)) + return -EBUSY; + + vc->rx_vcc = vcc; + set_bit(VCF_RX, &vc->flags); + + if ((vcc->vci == 3) || (vcc->vci == 4)) + return 0; + + flush_rx_pool(card, &vc->rcv.rx_pool); + + rcte |= SAR_RCTE_CONNECTOPEN; + rcte |= SAR_RCTE_RAWCELLINTEN; + + switch (qos->aal) { + case ATM_AAL0: + rcte |= SAR_RCTE_RCQ; + break; + case ATM_AAL1: + rcte |= SAR_RCTE_OAM; /* Let SAR drop Video */ + break; + case ATM_AAL34: + rcte |= SAR_RCTE_AAL34; + break; + case ATM_AAL5: + rcte |= SAR_RCTE_AAL5; + break; + default: + rcte |= SAR_RCTE_RCQ; + break; + } + + if (qos->aal != ATM_AAL5) + rcte |= SAR_RCTE_FBP_1; + else if (qos->rxtp.max_sdu > SAR_FB_SIZE_2) + rcte |= SAR_RCTE_FBP_3; + else if (qos->rxtp.max_sdu > SAR_FB_SIZE_1) + rcte |= SAR_RCTE_FBP_2; + else if (qos->rxtp.max_sdu > SAR_FB_SIZE_0) + rcte |= SAR_RCTE_FBP_1; + else + rcte |= SAR_RCTE_FBP_01; + + addr = card->rct_base + (vc->index << 2); + + OPRINTK("%s: writing RCT at 0x%lx\n", card->name, addr); + write_sram(card, addr, rcte); + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(SAR_CMD_OPEN_CONNECTION | (addr << 2), SAR_REG_CMD); + waitfor_idle(card); + spin_unlock_irqrestore(&card->cmd_lock, flags); + + return 0; +} + +static int +idt77252_open(struct atm_vcc *vcc) +{ + struct atm_dev *dev = vcc->dev; + struct idt77252_dev *card = dev->dev_data; + struct vc_map *vc; + unsigned int index; + unsigned int inuse; + int error; + int vci = vcc->vci; + short vpi = vcc->vpi; + + if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC) + return 0; + + if (vpi >= (1 << card->vpibits)) { + printk("%s: unsupported VPI: %d\n", card->name, vpi); + return -EINVAL; + } + + if (vci >= (1 << card->vcibits)) { + printk("%s: unsupported VCI: %d\n", card->name, vci); + return -EINVAL; + } + + set_bit(ATM_VF_ADDR, &vcc->flags); + + mutex_lock(&card->mutex); + + OPRINTK("%s: opening vpi.vci: %d.%d\n", card->name, vpi, vci); + + switch (vcc->qos.aal) { + case ATM_AAL0: + case ATM_AAL1: + case ATM_AAL5: + break; + default: + printk("%s: Unsupported AAL: %d\n", card->name, vcc->qos.aal); + mutex_unlock(&card->mutex); + return -EPROTONOSUPPORT; + } + + index = VPCI2VC(card, vpi, vci); + if (!card->vcs[index]) { + card->vcs[index] = kzalloc(sizeof(struct vc_map), GFP_KERNEL); + if (!card->vcs[index]) { + printk("%s: can't alloc vc in open()\n", card->name); + mutex_unlock(&card->mutex); + return -ENOMEM; + } + card->vcs[index]->card = card; + card->vcs[index]->index = index; + + spin_lock_init(&card->vcs[index]->lock); + } + vc = card->vcs[index]; + + vcc->dev_data = vc; + + IPRINTK("%s: idt77252_open: vc = %d (%d.%d) %s/%s (max RX SDU: %u)\n", + card->name, vc->index, vcc->vpi, vcc->vci, + vcc->qos.rxtp.traffic_class != ATM_NONE ? "rx" : "--", + vcc->qos.txtp.traffic_class != ATM_NONE ? "tx" : "--", + vcc->qos.rxtp.max_sdu); + + inuse = 0; + if (vcc->qos.txtp.traffic_class != ATM_NONE && + test_bit(VCF_TX, &vc->flags)) + inuse = 1; + if (vcc->qos.rxtp.traffic_class != ATM_NONE && + test_bit(VCF_RX, &vc->flags)) + inuse += 2; + + if (inuse) { + printk("%s: %s vci already in use.\n", card->name, + inuse == 1 ? "tx" : inuse == 2 ? "rx" : "tx and rx"); + mutex_unlock(&card->mutex); + return -EADDRINUSE; + } + + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + error = idt77252_init_tx(card, vc, vcc, &vcc->qos); + if (error) { + mutex_unlock(&card->mutex); + return error; + } + } + + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + error = idt77252_init_rx(card, vc, vcc, &vcc->qos); + if (error) { + mutex_unlock(&card->mutex); + return error; + } + } + + set_bit(ATM_VF_READY, &vcc->flags); + + mutex_unlock(&card->mutex); + return 0; +} + +static void +idt77252_close(struct atm_vcc *vcc) +{ + struct atm_dev *dev = vcc->dev; + struct idt77252_dev *card = dev->dev_data; + struct vc_map *vc = vcc->dev_data; + unsigned long flags; + unsigned long addr; + unsigned long timeout; + + mutex_lock(&card->mutex); + + IPRINTK("%s: idt77252_close: vc = %d (%d.%d)\n", + card->name, vc->index, vcc->vpi, vcc->vci); + + clear_bit(ATM_VF_READY, &vcc->flags); + + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + + spin_lock_irqsave(&vc->lock, flags); + clear_bit(VCF_RX, &vc->flags); + vc->rx_vcc = NULL; + spin_unlock_irqrestore(&vc->lock, flags); + + if ((vcc->vci == 3) || (vcc->vci == 4)) + goto done; + + addr = card->rct_base + vc->index * SAR_SRAM_RCT_SIZE; + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(SAR_CMD_CLOSE_CONNECTION | (addr << 2), SAR_REG_CMD); + waitfor_idle(card); + spin_unlock_irqrestore(&card->cmd_lock, flags); + + if (skb_queue_len(&vc->rcv.rx_pool.queue) != 0) { + DPRINTK("%s: closing a VC with pending rx buffers.\n", + card->name); + + recycle_rx_pool_skb(card, &vc->rcv.rx_pool); + } + } + +done: + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + + spin_lock_irqsave(&vc->lock, flags); + clear_bit(VCF_TX, &vc->flags); + clear_bit(VCF_IDLE, &vc->flags); + clear_bit(VCF_RSV, &vc->flags); + vc->tx_vcc = NULL; + + if (vc->estimator) { + del_timer(&vc->estimator->timer); + kfree(vc->estimator); + vc->estimator = NULL; + } + spin_unlock_irqrestore(&vc->lock, flags); + + timeout = 5 * 1000; + while (atomic_read(&vc->scq->used) > 0) { + timeout = msleep_interruptible(timeout); + if (!timeout) { + pr_warn("%s: SCQ drain timeout: %u used\n", + card->name, atomic_read(&vc->scq->used)); + break; + } + } + + writel(TCMDQ_HALT | vc->index, SAR_REG_TCMDQ); + clear_scd(card, vc->scq, vc->class); + + if (vc->class == SCHED_CBR) { + clear_tst(card, vc); + card->tst_free += vc->ntste; + vc->ntste = 0; + } + + card->scd2vc[vc->scd_index] = NULL; + free_scq(card, vc->scq); + } + + mutex_unlock(&card->mutex); +} + +static int +idt77252_change_qos(struct atm_vcc *vcc, struct atm_qos *qos, int flags) +{ + struct atm_dev *dev = vcc->dev; + struct idt77252_dev *card = dev->dev_data; + struct vc_map *vc = vcc->dev_data; + int error = 0; + + mutex_lock(&card->mutex); + + if (qos->txtp.traffic_class != ATM_NONE) { + if (!test_bit(VCF_TX, &vc->flags)) { + error = idt77252_init_tx(card, vc, vcc, qos); + if (error) + goto out; + } else { + switch (qos->txtp.traffic_class) { + case ATM_CBR: + error = idt77252_init_cbr(card, vc, vcc, qos); + if (error) + goto out; + break; + + case ATM_UBR: + error = idt77252_init_ubr(card, vc, vcc, qos); + if (error) + goto out; + + if (!test_bit(VCF_IDLE, &vc->flags)) { + writel(TCMDQ_LACR | (vc->lacr << 16) | + vc->index, SAR_REG_TCMDQ); + } + break; + + case ATM_VBR: + case ATM_ABR: + error = -EOPNOTSUPP; + goto out; + } + } + } + + if ((qos->rxtp.traffic_class != ATM_NONE) && + !test_bit(VCF_RX, &vc->flags)) { + error = idt77252_init_rx(card, vc, vcc, qos); + if (error) + goto out; + } + + memcpy(&vcc->qos, qos, sizeof(struct atm_qos)); + + set_bit(ATM_VF_HASQOS, &vcc->flags); + +out: + mutex_unlock(&card->mutex); + return error; +} + +static int +idt77252_proc_read(struct atm_dev *dev, loff_t * pos, char *page) +{ + struct idt77252_dev *card = dev->dev_data; + int i, left; + + left = (int) *pos; + if (!left--) + return sprintf(page, "IDT77252 Interrupts:\n"); + if (!left--) + return sprintf(page, "TSIF: %lu\n", card->irqstat[15]); + if (!left--) + return sprintf(page, "TXICP: %lu\n", card->irqstat[14]); + if (!left--) + return sprintf(page, "TSQF: %lu\n", card->irqstat[12]); + if (!left--) + return sprintf(page, "TMROF: %lu\n", card->irqstat[11]); + if (!left--) + return sprintf(page, "PHYI: %lu\n", card->irqstat[10]); + if (!left--) + return sprintf(page, "FBQ3A: %lu\n", card->irqstat[8]); + if (!left--) + return sprintf(page, "FBQ2A: %lu\n", card->irqstat[7]); + if (!left--) + return sprintf(page, "RSQF: %lu\n", card->irqstat[6]); + if (!left--) + return sprintf(page, "EPDU: %lu\n", card->irqstat[5]); + if (!left--) + return sprintf(page, "RAWCF: %lu\n", card->irqstat[4]); + if (!left--) + return sprintf(page, "FBQ1A: %lu\n", card->irqstat[3]); + if (!left--) + return sprintf(page, "FBQ0A: %lu\n", card->irqstat[2]); + if (!left--) + return sprintf(page, "RSQAF: %lu\n", card->irqstat[1]); + if (!left--) + return sprintf(page, "IDT77252 Transmit Connection Table:\n"); + + for (i = 0; i < card->tct_size; i++) { + unsigned long tct; + struct atm_vcc *vcc; + struct vc_map *vc; + char *p; + + vc = card->vcs[i]; + if (!vc) + continue; + + vcc = NULL; + if (vc->tx_vcc) + vcc = vc->tx_vcc; + if (!vcc) + continue; + if (left--) + continue; + + p = page; + p += sprintf(p, " %4u: %u.%u: ", i, vcc->vpi, vcc->vci); + tct = (unsigned long) (card->tct_base + i * SAR_SRAM_TCT_SIZE); + + for (i = 0; i < 8; i++) + p += sprintf(p, " %08x", read_sram(card, tct + i)); + p += sprintf(p, "\n"); + return p - page; + } + return 0; +} + +/*****************************************************************************/ +/* */ +/* Interrupt handler */ +/* */ +/*****************************************************************************/ + +static void +idt77252_collect_stat(struct idt77252_dev *card) +{ + (void) readl(SAR_REG_CDC); + (void) readl(SAR_REG_VPEC); + (void) readl(SAR_REG_ICC); + +} + +static irqreturn_t +idt77252_interrupt(int irq, void *dev_id) +{ + struct idt77252_dev *card = dev_id; + u32 stat; + + stat = readl(SAR_REG_STAT) & 0xffff; + if (!stat) /* no interrupt for us */ + return IRQ_NONE; + + if (test_and_set_bit(IDT77252_BIT_INTERRUPT, &card->flags)) { + printk("%s: Re-entering irq_handler()\n", card->name); + goto out; + } + + writel(stat, SAR_REG_STAT); /* reset interrupt */ + + if (stat & SAR_STAT_TSIF) { /* entry written to TSQ */ + INTPRINTK("%s: TSIF\n", card->name); + card->irqstat[15]++; + idt77252_tx(card); + } + if (stat & SAR_STAT_TXICP) { /* Incomplete CS-PDU has */ + INTPRINTK("%s: TXICP\n", card->name); + card->irqstat[14]++; +#ifdef CONFIG_ATM_IDT77252_DEBUG + idt77252_tx_dump(card); +#endif + } + if (stat & SAR_STAT_TSQF) { /* TSQ 7/8 full */ + INTPRINTK("%s: TSQF\n", card->name); + card->irqstat[12]++; + idt77252_tx(card); + } + if (stat & SAR_STAT_TMROF) { /* Timer overflow */ + INTPRINTK("%s: TMROF\n", card->name); + card->irqstat[11]++; + idt77252_collect_stat(card); + } + + if (stat & SAR_STAT_EPDU) { /* Got complete CS-PDU */ + INTPRINTK("%s: EPDU\n", card->name); + card->irqstat[5]++; + idt77252_rx(card); + } + if (stat & SAR_STAT_RSQAF) { /* RSQ is 7/8 full */ + INTPRINTK("%s: RSQAF\n", card->name); + card->irqstat[1]++; + idt77252_rx(card); + } + if (stat & SAR_STAT_RSQF) { /* RSQ is full */ + INTPRINTK("%s: RSQF\n", card->name); + card->irqstat[6]++; + idt77252_rx(card); + } + if (stat & SAR_STAT_RAWCF) { /* Raw cell received */ + INTPRINTK("%s: RAWCF\n", card->name); + card->irqstat[4]++; + idt77252_rx_raw(card); + } + + if (stat & SAR_STAT_PHYI) { /* PHY device interrupt */ + INTPRINTK("%s: PHYI", card->name); + card->irqstat[10]++; + if (card->atmdev->phy && card->atmdev->phy->interrupt) + card->atmdev->phy->interrupt(card->atmdev); + } + + if (stat & (SAR_STAT_FBQ0A | SAR_STAT_FBQ1A | + SAR_STAT_FBQ2A | SAR_STAT_FBQ3A)) { + + writel(readl(SAR_REG_CFG) & ~(SAR_CFG_FBIE), SAR_REG_CFG); + + INTPRINTK("%s: FBQA: %04x\n", card->name, stat); + + if (stat & SAR_STAT_FBQ0A) + card->irqstat[2]++; + if (stat & SAR_STAT_FBQ1A) + card->irqstat[3]++; + if (stat & SAR_STAT_FBQ2A) + card->irqstat[7]++; + if (stat & SAR_STAT_FBQ3A) + card->irqstat[8]++; + + schedule_work(&card->tqueue); + } + +out: + clear_bit(IDT77252_BIT_INTERRUPT, &card->flags); + return IRQ_HANDLED; +} + +static void +idt77252_softint(struct work_struct *work) +{ + struct idt77252_dev *card = + container_of(work, struct idt77252_dev, tqueue); + u32 stat; + int done; + + for (done = 1; ; done = 1) { + stat = readl(SAR_REG_STAT) >> 16; + + if ((stat & 0x0f) < SAR_FBQ0_HIGH) { + add_rx_skb(card, 0, SAR_FB_SIZE_0, 32); + done = 0; + } + + stat >>= 4; + if ((stat & 0x0f) < SAR_FBQ1_HIGH) { + add_rx_skb(card, 1, SAR_FB_SIZE_1, 32); + done = 0; + } + + stat >>= 4; + if ((stat & 0x0f) < SAR_FBQ2_HIGH) { + add_rx_skb(card, 2, SAR_FB_SIZE_2, 32); + done = 0; + } + + stat >>= 4; + if ((stat & 0x0f) < SAR_FBQ3_HIGH) { + add_rx_skb(card, 3, SAR_FB_SIZE_3, 32); + done = 0; + } + + if (done) + break; + } + + writel(readl(SAR_REG_CFG) | SAR_CFG_FBIE, SAR_REG_CFG); +} + + +static int +open_card_oam(struct idt77252_dev *card) +{ + unsigned long flags; + unsigned long addr; + struct vc_map *vc; + int vpi, vci; + int index; + u32 rcte; + + for (vpi = 0; vpi < (1 << card->vpibits); vpi++) { + for (vci = 3; vci < 5; vci++) { + index = VPCI2VC(card, vpi, vci); + + vc = kzalloc(sizeof(struct vc_map), GFP_KERNEL); + if (!vc) { + printk("%s: can't alloc vc\n", card->name); + return -ENOMEM; + } + vc->index = index; + card->vcs[index] = vc; + + flush_rx_pool(card, &vc->rcv.rx_pool); + + rcte = SAR_RCTE_CONNECTOPEN | + SAR_RCTE_RAWCELLINTEN | + SAR_RCTE_RCQ | + SAR_RCTE_FBP_1; + + addr = card->rct_base + (vc->index << 2); + write_sram(card, addr, rcte); + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(SAR_CMD_OPEN_CONNECTION | (addr << 2), + SAR_REG_CMD); + waitfor_idle(card); + spin_unlock_irqrestore(&card->cmd_lock, flags); + } + } + + return 0; +} + +static void +close_card_oam(struct idt77252_dev *card) +{ + unsigned long flags; + unsigned long addr; + struct vc_map *vc; + int vpi, vci; + int index; + + for (vpi = 0; vpi < (1 << card->vpibits); vpi++) { + for (vci = 3; vci < 5; vci++) { + index = VPCI2VC(card, vpi, vci); + vc = card->vcs[index]; + + addr = card->rct_base + vc->index * SAR_SRAM_RCT_SIZE; + + spin_lock_irqsave(&card->cmd_lock, flags); + writel(SAR_CMD_CLOSE_CONNECTION | (addr << 2), + SAR_REG_CMD); + waitfor_idle(card); + spin_unlock_irqrestore(&card->cmd_lock, flags); + + if (skb_queue_len(&vc->rcv.rx_pool.queue) != 0) { + DPRINTK("%s: closing a VC " + "with pending rx buffers.\n", + card->name); + + recycle_rx_pool_skb(card, &vc->rcv.rx_pool); + } + kfree(vc); + } + } +} + +static int +open_card_ubr0(struct idt77252_dev *card) +{ + struct vc_map *vc; + + vc = kzalloc(sizeof(struct vc_map), GFP_KERNEL); + if (!vc) { + printk("%s: can't alloc vc\n", card->name); + return -ENOMEM; + } + card->vcs[0] = vc; + vc->class = SCHED_UBR0; + + vc->scq = alloc_scq(card, vc->class); + if (!vc->scq) { + printk("%s: can't get SCQ.\n", card->name); + return -ENOMEM; + } + + card->scd2vc[0] = vc; + vc->scd_index = 0; + vc->scq->scd = card->scd_base; + + fill_scd(card, vc->scq, vc->class); + + write_sram(card, card->tct_base + 0, TCT_UBR | card->scd_base); + write_sram(card, card->tct_base + 1, 0); + write_sram(card, card->tct_base + 2, 0); + write_sram(card, card->tct_base + 3, 0); + write_sram(card, card->tct_base + 4, 0); + write_sram(card, card->tct_base + 5, 0); + write_sram(card, card->tct_base + 6, 0); + write_sram(card, card->tct_base + 7, TCT_FLAG_UBR); + + clear_bit(VCF_IDLE, &vc->flags); + writel(TCMDQ_START | 0, SAR_REG_TCMDQ); + return 0; +} + +static void +close_card_ubr0(struct idt77252_dev *card) +{ + struct vc_map *vc = card->vcs[0]; + + free_scq(card, vc->scq); + kfree(vc); +} + +static int +idt77252_dev_open(struct idt77252_dev *card) +{ + u32 conf; + + if (!test_bit(IDT77252_BIT_INIT, &card->flags)) { + printk("%s: SAR not yet initialized.\n", card->name); + return -1; + } + + conf = SAR_CFG_RXPTH| /* enable receive path */ + SAR_RX_DELAY | /* interrupt on complete PDU */ + SAR_CFG_RAWIE | /* interrupt enable on raw cells */ + SAR_CFG_RQFIE | /* interrupt on RSQ almost full */ + SAR_CFG_TMOIE | /* interrupt on timer overflow */ + SAR_CFG_FBIE | /* interrupt on low free buffers */ + SAR_CFG_TXEN | /* transmit operation enable */ + SAR_CFG_TXINT | /* interrupt on transmit status */ + SAR_CFG_TXUIE | /* interrupt on transmit underrun */ + SAR_CFG_TXSFI | /* interrupt on TSQ almost full */ + SAR_CFG_PHYIE /* enable PHY interrupts */ + ; + +#ifdef CONFIG_ATM_IDT77252_RCV_ALL + /* Test RAW cell receive. */ + conf |= SAR_CFG_VPECA; +#endif + + writel(readl(SAR_REG_CFG) | conf, SAR_REG_CFG); + + if (open_card_oam(card)) { + printk("%s: Error initializing OAM.\n", card->name); + return -1; + } + + if (open_card_ubr0(card)) { + printk("%s: Error initializing UBR0.\n", card->name); + return -1; + } + + IPRINTK("%s: opened IDT77252 ABR SAR.\n", card->name); + return 0; +} + +static void idt77252_dev_close(struct atm_dev *dev) +{ + struct idt77252_dev *card = dev->dev_data; + u32 conf; + + close_card_ubr0(card); + close_card_oam(card); + + conf = SAR_CFG_RXPTH | /* enable receive path */ + SAR_RX_DELAY | /* interrupt on complete PDU */ + SAR_CFG_RAWIE | /* interrupt enable on raw cells */ + SAR_CFG_RQFIE | /* interrupt on RSQ almost full */ + SAR_CFG_TMOIE | /* interrupt on timer overflow */ + SAR_CFG_FBIE | /* interrupt on low free buffers */ + SAR_CFG_TXEN | /* transmit operation enable */ + SAR_CFG_TXINT | /* interrupt on transmit status */ + SAR_CFG_TXUIE | /* interrupt on xmit underrun */ + SAR_CFG_TXSFI /* interrupt on TSQ almost full */ + ; + + writel(readl(SAR_REG_CFG) & ~(conf), SAR_REG_CFG); + + DIPRINTK("%s: closed IDT77252 ABR SAR.\n", card->name); +} + + +/*****************************************************************************/ +/* */ +/* Initialisation and Deinitialization of IDT77252 */ +/* */ +/*****************************************************************************/ + + +static void +deinit_card(struct idt77252_dev *card) +{ + struct sk_buff *skb; + int i, j; + + if (!test_bit(IDT77252_BIT_INIT, &card->flags)) { + printk("%s: SAR not yet initialized.\n", card->name); + return; + } + DIPRINTK("idt77252: deinitialize card %u\n", card->index); + + writel(0, SAR_REG_CFG); + + if (card->atmdev) + atm_dev_deregister(card->atmdev); + + for (i = 0; i < 4; i++) { + for (j = 0; j < FBQ_SIZE; j++) { + skb = card->sbpool[i].skb[j]; + if (skb) { + dma_unmap_single(&card->pcidev->dev, + IDT77252_PRV_PADDR(skb), + (skb_end_pointer(skb) - + skb->data), + DMA_FROM_DEVICE); + card->sbpool[i].skb[j] = NULL; + dev_kfree_skb(skb); + } + } + } + + vfree(card->soft_tst); + + vfree(card->scd2vc); + + vfree(card->vcs); + + if (card->raw_cell_hnd) { + dma_free_coherent(&card->pcidev->dev, 2 * sizeof(u32), + card->raw_cell_hnd, card->raw_cell_paddr); + } + + if (card->rsq.base) { + DIPRINTK("%s: Release RSQ ...\n", card->name); + deinit_rsq(card); + } + + if (card->tsq.base) { + DIPRINTK("%s: Release TSQ ...\n", card->name); + deinit_tsq(card); + } + + DIPRINTK("idt77252: Release IRQ.\n"); + free_irq(card->pcidev->irq, card); + + for (i = 0; i < 4; i++) { + if (card->fbq[i]) + iounmap(card->fbq[i]); + } + + if (card->membase) + iounmap(card->membase); + + clear_bit(IDT77252_BIT_INIT, &card->flags); + DIPRINTK("%s: Card deinitialized.\n", card->name); +} + + +static void init_sram(struct idt77252_dev *card) +{ + int i; + + for (i = 0; i < card->sramsize; i += 4) + write_sram(card, (i >> 2), 0); + + /* set SRAM layout for THIS card */ + if (card->sramsize == (512 * 1024)) { + card->tct_base = SAR_SRAM_TCT_128_BASE; + card->tct_size = (SAR_SRAM_TCT_128_TOP - card->tct_base + 1) + / SAR_SRAM_TCT_SIZE; + card->rct_base = SAR_SRAM_RCT_128_BASE; + card->rct_size = (SAR_SRAM_RCT_128_TOP - card->rct_base + 1) + / SAR_SRAM_RCT_SIZE; + card->rt_base = SAR_SRAM_RT_128_BASE; + card->scd_base = SAR_SRAM_SCD_128_BASE; + card->scd_size = (SAR_SRAM_SCD_128_TOP - card->scd_base + 1) + / SAR_SRAM_SCD_SIZE; + card->tst[0] = SAR_SRAM_TST1_128_BASE; + card->tst[1] = SAR_SRAM_TST2_128_BASE; + card->tst_size = SAR_SRAM_TST1_128_TOP - card->tst[0] + 1; + card->abrst_base = SAR_SRAM_ABRSTD_128_BASE; + card->abrst_size = SAR_ABRSTD_SIZE_8K; + card->fifo_base = SAR_SRAM_FIFO_128_BASE; + card->fifo_size = SAR_RXFD_SIZE_32K; + } else { + card->tct_base = SAR_SRAM_TCT_32_BASE; + card->tct_size = (SAR_SRAM_TCT_32_TOP - card->tct_base + 1) + / SAR_SRAM_TCT_SIZE; + card->rct_base = SAR_SRAM_RCT_32_BASE; + card->rct_size = (SAR_SRAM_RCT_32_TOP - card->rct_base + 1) + / SAR_SRAM_RCT_SIZE; + card->rt_base = SAR_SRAM_RT_32_BASE; + card->scd_base = SAR_SRAM_SCD_32_BASE; + card->scd_size = (SAR_SRAM_SCD_32_TOP - card->scd_base + 1) + / SAR_SRAM_SCD_SIZE; + card->tst[0] = SAR_SRAM_TST1_32_BASE; + card->tst[1] = SAR_SRAM_TST2_32_BASE; + card->tst_size = (SAR_SRAM_TST1_32_TOP - card->tst[0] + 1); + card->abrst_base = SAR_SRAM_ABRSTD_32_BASE; + card->abrst_size = SAR_ABRSTD_SIZE_1K; + card->fifo_base = SAR_SRAM_FIFO_32_BASE; + card->fifo_size = SAR_RXFD_SIZE_4K; + } + + /* Initialize TCT */ + for (i = 0; i < card->tct_size; i++) { + write_sram(card, i * SAR_SRAM_TCT_SIZE + 0, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 1, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 2, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 3, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 4, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 5, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 6, 0); + write_sram(card, i * SAR_SRAM_TCT_SIZE + 7, 0); + } + + /* Initialize RCT */ + for (i = 0; i < card->rct_size; i++) { + write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE, + (u32) SAR_RCTE_RAWCELLINTEN); + write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE + 1, + (u32) 0); + write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE + 2, + (u32) 0); + write_sram(card, card->rct_base + i * SAR_SRAM_RCT_SIZE + 3, + (u32) 0xffffffff); + } + + writel((SAR_FBQ0_LOW << 28) | (SAR_FB_SIZE_0 / 48), SAR_REG_FBQS0); + writel((SAR_FBQ1_LOW << 28) | (SAR_FB_SIZE_1 / 48), SAR_REG_FBQS1); + writel((SAR_FBQ2_LOW << 28) | (SAR_FB_SIZE_2 / 48), SAR_REG_FBQS2); + writel((SAR_FBQ3_LOW << 28) | (SAR_FB_SIZE_3 / 48), SAR_REG_FBQS3); + + /* Initialize rate table */ + for (i = 0; i < 256; i++) { + write_sram(card, card->rt_base + i, log_to_rate[i]); + } + + for (i = 0; i < 128; i++) { + unsigned int tmp; + + tmp = rate_to_log[(i << 2) + 0] << 0; + tmp |= rate_to_log[(i << 2) + 1] << 8; + tmp |= rate_to_log[(i << 2) + 2] << 16; + tmp |= rate_to_log[(i << 2) + 3] << 24; + write_sram(card, card->rt_base + 256 + i, tmp); + } + +#if 0 /* Fill RDF and AIR tables. */ + for (i = 0; i < 128; i++) { + unsigned int tmp; + + tmp = RDF[0][(i << 1) + 0] << 16; + tmp |= RDF[0][(i << 1) + 1] << 0; + write_sram(card, card->rt_base + 512 + i, tmp); + } + + for (i = 0; i < 128; i++) { + unsigned int tmp; + + tmp = AIR[0][(i << 1) + 0] << 16; + tmp |= AIR[0][(i << 1) + 1] << 0; + write_sram(card, card->rt_base + 640 + i, tmp); + } +#endif + + IPRINTK("%s: initialize rate table ...\n", card->name); + writel(card->rt_base << 2, SAR_REG_RTBL); + + /* Initialize TSTs */ + IPRINTK("%s: initialize TST ...\n", card->name); + card->tst_free = card->tst_size - 2; /* last two are jumps */ + + for (i = card->tst[0]; i < card->tst[0] + card->tst_size - 2; i++) + write_sram(card, i, TSTE_OPC_VAR); + write_sram(card, i++, TSTE_OPC_JMP | (card->tst[0] << 2)); + idt77252_sram_write_errors = 1; + write_sram(card, i++, TSTE_OPC_JMP | (card->tst[1] << 2)); + idt77252_sram_write_errors = 0; + for (i = card->tst[1]; i < card->tst[1] + card->tst_size - 2; i++) + write_sram(card, i, TSTE_OPC_VAR); + write_sram(card, i++, TSTE_OPC_JMP | (card->tst[1] << 2)); + idt77252_sram_write_errors = 1; + write_sram(card, i++, TSTE_OPC_JMP | (card->tst[0] << 2)); + idt77252_sram_write_errors = 0; + + card->tst_index = 0; + writel(card->tst[0] << 2, SAR_REG_TSTB); + + /* Initialize ABRSTD and Receive FIFO */ + IPRINTK("%s: initialize ABRSTD ...\n", card->name); + writel(card->abrst_size | (card->abrst_base << 2), + SAR_REG_ABRSTD); + + IPRINTK("%s: initialize receive fifo ...\n", card->name); + writel(card->fifo_size | (card->fifo_base << 2), + SAR_REG_RXFD); + + IPRINTK("%s: SRAM initialization complete.\n", card->name); +} + +static int init_card(struct atm_dev *dev) +{ + struct idt77252_dev *card = dev->dev_data; + struct pci_dev *pcidev = card->pcidev; + unsigned long tmpl, modl; + unsigned int linkrate, rsvdcr; + unsigned int tst_entries; + struct net_device *tmp; + char tname[10]; + + u32 size; + u_char pci_byte; + u32 conf; + int i, k; + + if (test_bit(IDT77252_BIT_INIT, &card->flags)) { + printk("Error: SAR already initialized.\n"); + return -1; + } + +/*****************************************************************/ +/* P C I C O N F I G U R A T I O N */ +/*****************************************************************/ + + /* Set PCI Retry-Timeout and TRDY timeout */ + IPRINTK("%s: Checking PCI retries.\n", card->name); + if (pci_read_config_byte(pcidev, 0x40, &pci_byte) != 0) { + printk("%s: can't read PCI retry timeout.\n", card->name); + deinit_card(card); + return -1; + } + if (pci_byte != 0) { + IPRINTK("%s: PCI retry timeout: %d, set to 0.\n", + card->name, pci_byte); + if (pci_write_config_byte(pcidev, 0x40, 0) != 0) { + printk("%s: can't set PCI retry timeout.\n", + card->name); + deinit_card(card); + return -1; + } + } + IPRINTK("%s: Checking PCI TRDY.\n", card->name); + if (pci_read_config_byte(pcidev, 0x41, &pci_byte) != 0) { + printk("%s: can't read PCI TRDY timeout.\n", card->name); + deinit_card(card); + return -1; + } + if (pci_byte != 0) { + IPRINTK("%s: PCI TRDY timeout: %d, set to 0.\n", + card->name, pci_byte); + if (pci_write_config_byte(pcidev, 0x41, 0) != 0) { + printk("%s: can't set PCI TRDY timeout.\n", card->name); + deinit_card(card); + return -1; + } + } + /* Reset Timer register */ + if (readl(SAR_REG_STAT) & SAR_STAT_TMROF) { + printk("%s: resetting timer overflow.\n", card->name); + writel(SAR_STAT_TMROF, SAR_REG_STAT); + } + IPRINTK("%s: Request IRQ ... ", card->name); + if (request_irq(pcidev->irq, idt77252_interrupt, IRQF_SHARED, + card->name, card) != 0) { + printk("%s: can't allocate IRQ.\n", card->name); + deinit_card(card); + return -1; + } + IPRINTK("got %d.\n", pcidev->irq); + +/*****************************************************************/ +/* C H E C K A N D I N I T S R A M */ +/*****************************************************************/ + + IPRINTK("%s: Initializing SRAM\n", card->name); + + /* preset size of connecton table, so that init_sram() knows about it */ + conf = SAR_CFG_TX_FIFO_SIZE_9 | /* Use maximum fifo size */ + SAR_CFG_RXSTQ_SIZE_8k | /* Receive Status Queue is 8k */ + SAR_CFG_IDLE_CLP | /* Set CLP on idle cells */ +#ifndef ATM_IDT77252_SEND_IDLE + SAR_CFG_NO_IDLE | /* Do not send idle cells */ +#endif + 0; + + if (card->sramsize == (512 * 1024)) + conf |= SAR_CFG_CNTBL_1k; + else + conf |= SAR_CFG_CNTBL_512; + + switch (vpibits) { + case 0: + conf |= SAR_CFG_VPVCS_0; + break; + default: + case 1: + conf |= SAR_CFG_VPVCS_1; + break; + case 2: + conf |= SAR_CFG_VPVCS_2; + break; + case 8: + conf |= SAR_CFG_VPVCS_8; + break; + } + + writel(readl(SAR_REG_CFG) | conf, SAR_REG_CFG); + + init_sram(card); + +/********************************************************************/ +/* A L L O C R A M A N D S E T V A R I O U S T H I N G S */ +/********************************************************************/ + /* Initialize TSQ */ + if (0 != init_tsq(card)) { + deinit_card(card); + return -1; + } + /* Initialize RSQ */ + if (0 != init_rsq(card)) { + deinit_card(card); + return -1; + } + + card->vpibits = vpibits; + if (card->sramsize == (512 * 1024)) { + card->vcibits = 10 - card->vpibits; + } else { + card->vcibits = 9 - card->vpibits; + } + + card->vcimask = 0; + for (k = 0, i = 1; k < card->vcibits; k++) { + card->vcimask |= i; + i <<= 1; + } + + IPRINTK("%s: Setting VPI/VCI mask to zero.\n", card->name); + writel(0, SAR_REG_VPM); + + /* Little Endian Order */ + writel(0, SAR_REG_GP); + + /* Initialize RAW Cell Handle Register */ + card->raw_cell_hnd = dma_alloc_coherent(&card->pcidev->dev, + 2 * sizeof(u32), + &card->raw_cell_paddr, + GFP_KERNEL); + if (!card->raw_cell_hnd) { + printk("%s: memory allocation failure.\n", card->name); + deinit_card(card); + return -1; + } + writel(card->raw_cell_paddr, SAR_REG_RAWHND); + IPRINTK("%s: raw cell handle is at 0x%p.\n", card->name, + card->raw_cell_hnd); + + size = sizeof(struct vc_map *) * card->tct_size; + IPRINTK("%s: allocate %d byte for VC map.\n", card->name, size); + card->vcs = vzalloc(size); + if (!card->vcs) { + printk("%s: memory allocation failure.\n", card->name); + deinit_card(card); + return -1; + } + + size = sizeof(struct vc_map *) * card->scd_size; + IPRINTK("%s: allocate %d byte for SCD to VC mapping.\n", + card->name, size); + card->scd2vc = vzalloc(size); + if (!card->scd2vc) { + printk("%s: memory allocation failure.\n", card->name); + deinit_card(card); + return -1; + } + + size = sizeof(struct tst_info) * (card->tst_size - 2); + IPRINTK("%s: allocate %d byte for TST to VC mapping.\n", + card->name, size); + card->soft_tst = vmalloc(size); + if (!card->soft_tst) { + printk("%s: memory allocation failure.\n", card->name); + deinit_card(card); + return -1; + } + for (i = 0; i < card->tst_size - 2; i++) { + card->soft_tst[i].tste = TSTE_OPC_VAR; + card->soft_tst[i].vc = NULL; + } + + if (dev->phy == NULL) { + printk("%s: No LT device defined.\n", card->name); + deinit_card(card); + return -1; + } + if (dev->phy->ioctl == NULL) { + printk("%s: LT had no IOCTL function defined.\n", card->name); + deinit_card(card); + return -1; + } + +#ifdef CONFIG_ATM_IDT77252_USE_SUNI + /* + * this is a jhs hack to get around special functionality in the + * phy driver for the atecom hardware; the functionality doesn't + * exist in the linux atm suni driver + * + * it isn't the right way to do things, but as the guy from NIST + * said, talking about their measurement of the fine structure + * constant, "it's good enough for government work." + */ + linkrate = 149760000; +#endif + + card->link_pcr = (linkrate / 8 / 53); + printk("%s: Linkrate on ATM line : %u bit/s, %u cell/s.\n", + card->name, linkrate, card->link_pcr); + +#ifdef ATM_IDT77252_SEND_IDLE + card->utopia_pcr = card->link_pcr; +#else + card->utopia_pcr = (160000000 / 8 / 54); +#endif + + rsvdcr = 0; + if (card->utopia_pcr > card->link_pcr) + rsvdcr = card->utopia_pcr - card->link_pcr; + + tmpl = (unsigned long) rsvdcr * ((unsigned long) card->tst_size - 2); + modl = tmpl % (unsigned long)card->utopia_pcr; + tst_entries = (int) (tmpl / (unsigned long)card->utopia_pcr); + if (modl) + tst_entries++; + card->tst_free -= tst_entries; + fill_tst(card, NULL, tst_entries, TSTE_OPC_NULL); + +#ifdef HAVE_EEPROM + idt77252_eeprom_init(card); + printk("%s: EEPROM: %02x:", card->name, + idt77252_eeprom_read_status(card)); + + for (i = 0; i < 0x80; i++) { + printk(" %02x", + idt77252_eeprom_read_byte(card, i) + ); + } + printk("\n"); +#endif /* HAVE_EEPROM */ + + /* + * XXX: + */ + sprintf(tname, "eth%d", card->index); + tmp = dev_get_by_name(&init_net, tname); /* jhs: was "tmp = dev_get(tname);" */ + if (tmp) { + memcpy(card->atmdev->esi, tmp->dev_addr, 6); + dev_put(tmp); + printk("%s: ESI %pM\n", card->name, card->atmdev->esi); + } + /* + * XXX: + */ + + /* Set Maximum Deficit Count for now. */ + writel(0xffff, SAR_REG_MDFCT); + + set_bit(IDT77252_BIT_INIT, &card->flags); + + XPRINTK("%s: IDT77252 ABR SAR initialization complete.\n", card->name); + return 0; +} + + +/*****************************************************************************/ +/* */ +/* Probing of IDT77252 ABR SAR */ +/* */ +/*****************************************************************************/ + + +static int idt77252_preset(struct idt77252_dev *card) +{ + u16 pci_command; + +/*****************************************************************/ +/* P C I C O N F I G U R A T I O N */ +/*****************************************************************/ + + XPRINTK("%s: Enable PCI master and memory access for SAR.\n", + card->name); + if (pci_read_config_word(card->pcidev, PCI_COMMAND, &pci_command)) { + printk("%s: can't read PCI_COMMAND.\n", card->name); + deinit_card(card); + return -1; + } + if (!(pci_command & PCI_COMMAND_IO)) { + printk("%s: PCI_COMMAND: %04x (???)\n", + card->name, pci_command); + deinit_card(card); + return (-1); + } + pci_command |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); + if (pci_write_config_word(card->pcidev, PCI_COMMAND, pci_command)) { + printk("%s: can't write PCI_COMMAND.\n", card->name); + deinit_card(card); + return -1; + } +/*****************************************************************/ +/* G E N E R I C R E S E T */ +/*****************************************************************/ + + /* Software reset */ + writel(SAR_CFG_SWRST, SAR_REG_CFG); + mdelay(1); + writel(0, SAR_REG_CFG); + + IPRINTK("%s: Software resetted.\n", card->name); + return 0; +} + + +static unsigned long probe_sram(struct idt77252_dev *card) +{ + u32 data, addr; + + writel(0, SAR_REG_DR0); + writel(SAR_CMD_WRITE_SRAM | (0 << 2), SAR_REG_CMD); + + for (addr = 0x4000; addr < 0x80000; addr += 0x4000) { + writel(ATM_POISON, SAR_REG_DR0); + writel(SAR_CMD_WRITE_SRAM | (addr << 2), SAR_REG_CMD); + + writel(SAR_CMD_READ_SRAM | (0 << 2), SAR_REG_CMD); + data = readl(SAR_REG_DR0); + + if (data != 0) + break; + } + + return addr * sizeof(u32); +} + +static int idt77252_init_one(struct pci_dev *pcidev, + const struct pci_device_id *id) +{ + static struct idt77252_dev **last = &idt77252_chain; + static int index = 0; + + unsigned long membase, srambase; + struct idt77252_dev *card; + struct atm_dev *dev; + int i, err; + + + if ((err = pci_enable_device(pcidev))) { + printk("idt77252: can't enable PCI device at %s\n", pci_name(pcidev)); + return err; + } + + if ((err = dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(32)))) { + printk("idt77252: can't enable DMA for PCI device at %s\n", pci_name(pcidev)); + goto err_out_disable_pdev; + } + + card = kzalloc(sizeof(struct idt77252_dev), GFP_KERNEL); + if (!card) { + printk("idt77252-%d: can't allocate private data\n", index); + err = -ENOMEM; + goto err_out_disable_pdev; + } + card->revision = pcidev->revision; + card->index = index; + card->pcidev = pcidev; + sprintf(card->name, "idt77252-%d", card->index); + + INIT_WORK(&card->tqueue, idt77252_softint); + + membase = pci_resource_start(pcidev, 1); + srambase = pci_resource_start(pcidev, 2); + + mutex_init(&card->mutex); + spin_lock_init(&card->cmd_lock); + spin_lock_init(&card->tst_lock); + + timer_setup(&card->tst_timer, tst_timer, 0); + + /* Do the I/O remapping... */ + card->membase = ioremap(membase, 1024); + if (!card->membase) { + printk("%s: can't ioremap() membase\n", card->name); + err = -EIO; + goto err_out_free_card; + } + + if (idt77252_preset(card)) { + printk("%s: preset failed\n", card->name); + err = -EIO; + goto err_out_iounmap; + } + + dev = atm_dev_register("idt77252", &pcidev->dev, &idt77252_ops, -1, + NULL); + if (!dev) { + printk("%s: can't register atm device\n", card->name); + err = -EIO; + goto err_out_iounmap; + } + dev->dev_data = card; + card->atmdev = dev; + +#ifdef CONFIG_ATM_IDT77252_USE_SUNI + suni_init(dev); + if (!dev->phy) { + printk("%s: can't init SUNI\n", card->name); + err = -EIO; + goto err_out_deinit_card; + } +#endif /* CONFIG_ATM_IDT77252_USE_SUNI */ + + card->sramsize = probe_sram(card); + + for (i = 0; i < 4; i++) { + card->fbq[i] = ioremap(srambase | 0x200000 | (i << 18), 4); + if (!card->fbq[i]) { + printk("%s: can't ioremap() FBQ%d\n", card->name, i); + err = -EIO; + goto err_out_deinit_card; + } + } + + printk("%s: ABR SAR (Rev %c): MEM %08lx SRAM %08lx [%u KB]\n", + card->name, ((card->revision > 1) && (card->revision < 25)) ? + 'A' + card->revision - 1 : '?', membase, srambase, + card->sramsize / 1024); + + if (init_card(dev)) { + printk("%s: init_card failed\n", card->name); + err = -EIO; + goto err_out_deinit_card; + } + + dev->ci_range.vpi_bits = card->vpibits; + dev->ci_range.vci_bits = card->vcibits; + dev->link_rate = card->link_pcr; + + if (dev->phy->start) + dev->phy->start(dev); + + if (idt77252_dev_open(card)) { + printk("%s: dev_open failed\n", card->name); + err = -EIO; + goto err_out_stop; + } + + *last = card; + last = &card->next; + index++; + + return 0; + +err_out_stop: + if (dev->phy->stop) + dev->phy->stop(dev); + +err_out_deinit_card: + deinit_card(card); + +err_out_iounmap: + iounmap(card->membase); + +err_out_free_card: + kfree(card); + +err_out_disable_pdev: + pci_disable_device(pcidev); + return err; +} + +static const struct pci_device_id idt77252_pci_tbl[] = +{ + { PCI_VDEVICE(IDT, PCI_DEVICE_ID_IDT_IDT77252), 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, idt77252_pci_tbl); + +static struct pci_driver idt77252_driver = { + .name = "idt77252", + .id_table = idt77252_pci_tbl, + .probe = idt77252_init_one, +}; + +static int __init idt77252_init(void) +{ + struct sk_buff *skb; + + printk("%s: at %p\n", __func__, idt77252_init); + + if (sizeof(skb->cb) < sizeof(struct atm_skb_data) + + sizeof(struct idt77252_skb_prv)) { + printk(KERN_ERR "%s: skb->cb is too small (%lu < %lu)\n", + __func__, (unsigned long) sizeof(skb->cb), + (unsigned long) sizeof(struct atm_skb_data) + + sizeof(struct idt77252_skb_prv)); + return -EIO; + } + + return pci_register_driver(&idt77252_driver); +} + +static void __exit idt77252_exit(void) +{ + struct idt77252_dev *card; + struct atm_dev *dev; + + pci_unregister_driver(&idt77252_driver); + + while (idt77252_chain) { + card = idt77252_chain; + dev = card->atmdev; + idt77252_chain = card->next; + del_timer_sync(&card->tst_timer); + + if (dev->phy->stop) + dev->phy->stop(dev); + deinit_card(card); + pci_disable_device(card->pcidev); + kfree(card); + } + + DIPRINTK("idt77252: finished cleanup-module().\n"); +} + +module_init(idt77252_init); +module_exit(idt77252_exit); + +MODULE_LICENSE("GPL"); + +module_param(vpibits, uint, 0); +MODULE_PARM_DESC(vpibits, "number of VPI bits supported (0, 1, or 2)"); +#ifdef CONFIG_ATM_IDT77252_DEBUG +module_param(debug, ulong, 0644); +MODULE_PARM_DESC(debug, "debug bitmap, see drivers/atm/idt77252.h"); +#endif + +MODULE_AUTHOR("Eddie C. Dost "); +MODULE_DESCRIPTION("IDT77252 ABR SAR Driver"); diff --git a/drivers/atm/idt77252.h b/drivers/atm/idt77252.h new file mode 100644 index 000000000..9339197d7 --- /dev/null +++ b/drivers/atm/idt77252.h @@ -0,0 +1,816 @@ +/******************************************************************* + * + * Copyright (c) 2000 ATecoM GmbH + * + * The author may be reached at ecd@atecom.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. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 _IDT77252_H +#define _IDT77252_H 1 + + +#include +#include +#include +#include + +/*****************************************************************************/ +/* */ +/* Makros */ +/* */ +/*****************************************************************************/ +#define VPCI2VC(card, vpi, vci) \ + (((vpi) << card->vcibits) | ((vci) & card->vcimask)) + +/*****************************************************************************/ +/* */ +/* DEBUGGING definitions */ +/* */ +/*****************************************************************************/ + +#define DBG_RAW_CELL 0x00000400 +#define DBG_TINY 0x00000200 +#define DBG_GENERAL 0x00000100 +#define DBG_XGENERAL 0x00000080 +#define DBG_INIT 0x00000040 +#define DBG_DEINIT 0x00000020 +#define DBG_INTERRUPT 0x00000010 +#define DBG_OPEN_CONN 0x00000008 +#define DBG_CLOSE_CONN 0x00000004 +#define DBG_RX_DATA 0x00000002 +#define DBG_TX_DATA 0x00000001 + +#ifdef CONFIG_ATM_IDT77252_DEBUG + +#define CPRINTK(args...) do { if (debug & DBG_CLOSE_CONN) printk(args); } while(0) +#define OPRINTK(args...) do { if (debug & DBG_OPEN_CONN) printk(args); } while(0) +#define IPRINTK(args...) do { if (debug & DBG_INIT) printk(args); } while(0) +#define INTPRINTK(args...) do { if (debug & DBG_INTERRUPT) printk(args); } while(0) +#define DIPRINTK(args...) do { if (debug & DBG_DEINIT) printk(args); } while(0) +#define TXPRINTK(args...) do { if (debug & DBG_TX_DATA) printk(args); } while(0) +#define RXPRINTK(args...) do { if (debug & DBG_RX_DATA) printk(args); } while(0) +#define XPRINTK(args...) do { if (debug & DBG_XGENERAL) printk(args); } while(0) +#define DPRINTK(args...) do { if (debug & DBG_GENERAL) printk(args); } while(0) +#define NPRINTK(args...) do { if (debug & DBG_TINY) printk(args); } while(0) +#define RPRINTK(args...) do { if (debug & DBG_RAW_CELL) printk(args); } while(0) + +#else + +#define CPRINTK(args...) do { } while(0) +#define OPRINTK(args...) do { } while(0) +#define IPRINTK(args...) do { } while(0) +#define INTPRINTK(args...) do { } while(0) +#define DIPRINTK(args...) do { } while(0) +#define TXPRINTK(args...) do { } while(0) +#define RXPRINTK(args...) do { } while(0) +#define XPRINTK(args...) do { } while(0) +#define DPRINTK(args...) do { } while(0) +#define NPRINTK(args...) do { } while(0) +#define RPRINTK(args...) do { } while(0) + +#endif + +#define SCHED_UBR0 0 +#define SCHED_UBR 1 +#define SCHED_VBR 2 +#define SCHED_ABR 3 +#define SCHED_CBR 4 + +#define SCQFULL_TIMEOUT HZ + +/*****************************************************************************/ +/* */ +/* Free Buffer Queue Layout */ +/* */ +/*****************************************************************************/ +#define SAR_FB_SIZE_0 (2048 - 256) +#define SAR_FB_SIZE_1 (4096 - 256) +#define SAR_FB_SIZE_2 (8192 - 256) +#define SAR_FB_SIZE_3 (16384 - 256) + +#define SAR_FBQ0_LOW 4 +#define SAR_FBQ0_HIGH 8 +#define SAR_FBQ1_LOW 2 +#define SAR_FBQ1_HIGH 4 +#define SAR_FBQ2_LOW 1 +#define SAR_FBQ2_HIGH 2 +#define SAR_FBQ3_LOW 1 +#define SAR_FBQ3_HIGH 2 + +#if 0 +#define SAR_TST_RESERVED 44 /* Num TST reserved for UBR/ABR/VBR */ +#else +#define SAR_TST_RESERVED 0 /* Num TST reserved for UBR/ABR/VBR */ +#endif + +#define TCT_CBR 0x00000000 +#define TCT_UBR 0x00000000 +#define TCT_VBR 0x40000000 +#define TCT_ABR 0x80000000 +#define TCT_TYPE 0xc0000000 + +#define TCT_RR 0x20000000 +#define TCT_LMCR 0x08000000 +#define TCT_SCD_MASK 0x0007ffff + +#define TCT_TSIF 0x00004000 +#define TCT_HALT 0x80000000 +#define TCT_IDLE 0x40000000 +#define TCT_FLAG_UBR 0x80000000 + +/*****************************************************************************/ +/* */ +/* Structure describing an IDT77252 */ +/* */ +/*****************************************************************************/ + +struct scqe +{ + u32 word_1; + u32 word_2; + u32 word_3; + u32 word_4; +}; + +#define SCQ_ENTRIES 64 +#define SCQ_SIZE (SCQ_ENTRIES * sizeof(struct scqe)) +#define SCQ_MASK (SCQ_SIZE - 1) + +struct scq_info +{ + struct scqe *base; + struct scqe *next; + struct scqe *last; + dma_addr_t paddr; + spinlock_t lock; + atomic_t used; + unsigned long trans_start; + unsigned long scd; + spinlock_t skblock; + struct sk_buff_head transmit; + struct sk_buff_head pending; +}; + +struct rx_pool { + struct sk_buff_head queue; + unsigned int len; +}; + +struct aal1 { + unsigned int total; + unsigned int count; + struct sk_buff *data; + unsigned char sequence; +}; + +struct vc_map; + +struct rate_estimator { + struct timer_list timer; + unsigned int interval; + unsigned int ewma_log; + u64 cells; + u64 last_cells; + long avcps; + u32 cps; + u32 maxcps; + struct vc_map *vc; +}; + +struct vc_map { + unsigned int index; + unsigned long flags; +#define VCF_TX 0 +#define VCF_RX 1 +#define VCF_IDLE 2 +#define VCF_RSV 3 + unsigned int class; + u8 init_er; + u8 lacr; + u8 max_er; + unsigned int ntste; + spinlock_t lock; + struct atm_vcc *tx_vcc; + struct atm_vcc *rx_vcc; + struct idt77252_dev *card; + struct scq_info *scq; /* To keep track of the SCQ */ + struct rate_estimator *estimator; + int scd_index; + union { + struct rx_pool rx_pool; + struct aal1 aal1; + } rcv; +}; + +/*****************************************************************************/ +/* */ +/* RCTE - Receive Connection Table Entry */ +/* */ +/*****************************************************************************/ + +struct rct_entry +{ + u32 word_1; + u32 buffer_handle; + u32 dma_address; + u32 aal5_crc32; +}; + +/*****************************************************************************/ +/* */ +/* RSQ - Receive Status Queue */ +/* */ +/*****************************************************************************/ + +#define SAR_RSQE_VALID 0x80000000 +#define SAR_RSQE_IDLE 0x40000000 +#define SAR_RSQE_BUF_MASK 0x00030000 +#define SAR_RSQE_BUF_ASGN 0x00008000 +#define SAR_RSQE_NZGFC 0x00004000 +#define SAR_RSQE_EPDU 0x00002000 +#define SAR_RSQE_BUF_CONT 0x00001000 +#define SAR_RSQE_EFCIE 0x00000800 +#define SAR_RSQE_CLP 0x00000400 +#define SAR_RSQE_CRC 0x00000200 +#define SAR_RSQE_CELLCNT 0x000001FF + + +#define RSQSIZE 8192 +#define RSQ_NUM_ENTRIES (RSQSIZE / 16) +#define RSQ_ALIGNMENT 8192 + +struct rsq_entry { + u32 word_1; + u32 word_2; + u32 word_3; + u32 word_4; +}; + +struct rsq_info { + struct rsq_entry *base; + struct rsq_entry *next; + struct rsq_entry *last; + dma_addr_t paddr; +}; + + +/*****************************************************************************/ +/* */ +/* TSQ - Transmit Status Queue */ +/* */ +/*****************************************************************************/ + +#define SAR_TSQE_INVALID 0x80000000 +#define SAR_TSQE_TIMESTAMP 0x00FFFFFF +#define SAR_TSQE_TYPE 0x60000000 +#define SAR_TSQE_TYPE_TIMER 0x00000000 +#define SAR_TSQE_TYPE_TSR 0x20000000 +#define SAR_TSQE_TYPE_IDLE 0x40000000 +#define SAR_TSQE_TYPE_TBD_COMP 0x60000000 + +#define SAR_TSQE_TAG(stat) (((stat) >> 24) & 0x1f) + +#define TSQSIZE 8192 +#define TSQ_NUM_ENTRIES 1024 +#define TSQ_ALIGNMENT 8192 + +struct tsq_entry +{ + u32 word_1; + u32 word_2; +}; + +struct tsq_info +{ + struct tsq_entry *base; + struct tsq_entry *next; + struct tsq_entry *last; + dma_addr_t paddr; +}; + +struct tst_info +{ + struct vc_map *vc; + u32 tste; +}; + +#define TSTE_MASK 0x601fffff + +#define TSTE_OPC_MASK 0x60000000 +#define TSTE_OPC_NULL 0x00000000 +#define TSTE_OPC_CBR 0x20000000 +#define TSTE_OPC_VAR 0x40000000 +#define TSTE_OPC_JMP 0x60000000 + +#define TSTE_PUSH_IDLE 0x01000000 +#define TSTE_PUSH_ACTIVE 0x02000000 + +#define TST_SWITCH_DONE 0 +#define TST_SWITCH_PENDING 1 +#define TST_SWITCH_WAIT 2 + +#define FBQ_SHIFT 9 +#define FBQ_SIZE (1 << FBQ_SHIFT) +#define FBQ_MASK (FBQ_SIZE - 1) + +struct sb_pool +{ + unsigned int index; + struct sk_buff *skb[FBQ_SIZE]; +}; + +#define POOL_HANDLE(queue, index) (((queue + 1) << 16) | (index)) +#define POOL_QUEUE(handle) (((handle) >> 16) - 1) +#define POOL_INDEX(handle) ((handle) & 0xffff) + +struct idt77252_dev +{ + struct tsq_info tsq; /* Transmit Status Queue */ + struct rsq_info rsq; /* Receive Status Queue */ + + struct pci_dev *pcidev; /* PCI handle (desriptor) */ + struct atm_dev *atmdev; /* ATM device desriptor */ + + void __iomem *membase; /* SAR's memory base address */ + unsigned long srambase; /* SAR's sram base address */ + void __iomem *fbq[4]; /* FBQ fill addresses */ + + struct mutex mutex; + spinlock_t cmd_lock; /* for r/w utility/sram */ + + unsigned long softstat; + unsigned long flags; /* see blow */ + + struct work_struct tqueue; + + unsigned long tct_base; /* TCT base address in SRAM */ + unsigned long rct_base; /* RCT base address in SRAM */ + unsigned long rt_base; /* Rate Table base in SRAM */ + unsigned long scd_base; /* SCD base address in SRAM */ + unsigned long tst[2]; /* TST base address in SRAM */ + unsigned long abrst_base; /* ABRST base address in SRAM */ + unsigned long fifo_base; /* RX FIFO base in SRAM */ + + unsigned long irqstat[16]; + + unsigned int sramsize; /* SAR's sram size */ + + unsigned int tct_size; /* total TCT entries */ + unsigned int rct_size; /* total RCT entries */ + unsigned int scd_size; /* length of SCD */ + unsigned int tst_size; /* total TST entries */ + unsigned int tst_free; /* free TSTEs in TST */ + unsigned int abrst_size; /* size of ABRST in words */ + unsigned int fifo_size; /* size of RX FIFO in words */ + + unsigned int vpibits; /* Bits used for VPI index */ + unsigned int vcibits; /* Bits used for VCI index */ + unsigned int vcimask; /* Mask for VCI index */ + + unsigned int utopia_pcr; /* Utopia Itf's Cell Rate */ + unsigned int link_pcr; /* PHY's Peek Cell Rate */ + + struct vc_map **vcs; /* Open Connections */ + struct vc_map **scd2vc; /* SCD to Connection map */ + + struct tst_info *soft_tst; /* TST to Connection map */ + unsigned int tst_index; /* Current TST in use */ + struct timer_list tst_timer; + spinlock_t tst_lock; + unsigned long tst_state; + + struct sb_pool sbpool[4]; /* Pool of RX skbuffs */ + struct sk_buff *raw_cell_head; /* Pointer to raw cell queue */ + u32 *raw_cell_hnd; /* Pointer to RCQ handle */ + dma_addr_t raw_cell_paddr; + + int index; /* SAR's ID */ + int revision; /* chip revision */ + + char name[16]; /* Device name */ + + struct idt77252_dev *next; +}; + + +/* definition for flag field above */ +#define IDT77252_BIT_INIT 1 +#define IDT77252_BIT_INTERRUPT 2 + + +#define ATM_CELL_PAYLOAD 48 + +#define FREEBUF_ALIGNMENT 16 + +/*****************************************************************************/ +/* */ +/* Makros */ +/* */ +/*****************************************************************************/ +#define ALIGN_ADDRESS(addr, alignment) \ + ((((u32)(addr)) + (((u32)(alignment))-1)) & ~(((u32)(alignment)) - 1)) + + +/*****************************************************************************/ +/* */ +/* ABR SAR Network operation Register */ +/* */ +/*****************************************************************************/ + +#define SAR_REG_DR0 (card->membase + 0x00) +#define SAR_REG_DR1 (card->membase + 0x04) +#define SAR_REG_DR2 (card->membase + 0x08) +#define SAR_REG_DR3 (card->membase + 0x0C) +#define SAR_REG_CMD (card->membase + 0x10) +#define SAR_REG_CFG (card->membase + 0x14) +#define SAR_REG_STAT (card->membase + 0x18) +#define SAR_REG_RSQB (card->membase + 0x1C) +#define SAR_REG_RSQT (card->membase + 0x20) +#define SAR_REG_RSQH (card->membase + 0x24) +#define SAR_REG_CDC (card->membase + 0x28) +#define SAR_REG_VPEC (card->membase + 0x2C) +#define SAR_REG_ICC (card->membase + 0x30) +#define SAR_REG_RAWCT (card->membase + 0x34) +#define SAR_REG_TMR (card->membase + 0x38) +#define SAR_REG_TSTB (card->membase + 0x3C) +#define SAR_REG_TSQB (card->membase + 0x40) +#define SAR_REG_TSQT (card->membase + 0x44) +#define SAR_REG_TSQH (card->membase + 0x48) +#define SAR_REG_GP (card->membase + 0x4C) +#define SAR_REG_VPM (card->membase + 0x50) +#define SAR_REG_RXFD (card->membase + 0x54) +#define SAR_REG_RXFT (card->membase + 0x58) +#define SAR_REG_RXFH (card->membase + 0x5C) +#define SAR_REG_RAWHND (card->membase + 0x60) +#define SAR_REG_RXSTAT (card->membase + 0x64) +#define SAR_REG_ABRSTD (card->membase + 0x68) +#define SAR_REG_ABRRQ (card->membase + 0x6C) +#define SAR_REG_VBRRQ (card->membase + 0x70) +#define SAR_REG_RTBL (card->membase + 0x74) +#define SAR_REG_MDFCT (card->membase + 0x78) +#define SAR_REG_TXSTAT (card->membase + 0x7C) +#define SAR_REG_TCMDQ (card->membase + 0x80) +#define SAR_REG_IRCP (card->membase + 0x84) +#define SAR_REG_FBQP0 (card->membase + 0x88) +#define SAR_REG_FBQP1 (card->membase + 0x8C) +#define SAR_REG_FBQP2 (card->membase + 0x90) +#define SAR_REG_FBQP3 (card->membase + 0x94) +#define SAR_REG_FBQS0 (card->membase + 0x98) +#define SAR_REG_FBQS1 (card->membase + 0x9C) +#define SAR_REG_FBQS2 (card->membase + 0xA0) +#define SAR_REG_FBQS3 (card->membase + 0xA4) +#define SAR_REG_FBQWP0 (card->membase + 0xA8) +#define SAR_REG_FBQWP1 (card->membase + 0xAC) +#define SAR_REG_FBQWP2 (card->membase + 0xB0) +#define SAR_REG_FBQWP3 (card->membase + 0xB4) +#define SAR_REG_NOW (card->membase + 0xB8) + + +/*****************************************************************************/ +/* */ +/* Commands */ +/* */ +/*****************************************************************************/ + +#define SAR_CMD_NO_OPERATION 0x00000000 +#define SAR_CMD_OPENCLOSE_CONNECTION 0x20000000 +#define SAR_CMD_WRITE_SRAM 0x40000000 +#define SAR_CMD_READ_SRAM 0x50000000 +#define SAR_CMD_READ_UTILITY 0x80000000 +#define SAR_CMD_WRITE_UTILITY 0x90000000 + +#define SAR_CMD_OPEN_CONNECTION (SAR_CMD_OPENCLOSE_CONNECTION | 0x00080000) +#define SAR_CMD_CLOSE_CONNECTION SAR_CMD_OPENCLOSE_CONNECTION + + +/*****************************************************************************/ +/* */ +/* Configuration Register bits */ +/* */ +/*****************************************************************************/ + +#define SAR_CFG_SWRST 0x80000000 /* Software reset */ +#define SAR_CFG_LOOP 0x40000000 /* Internal Loopback */ +#define SAR_CFG_RXPTH 0x20000000 /* Receive Path Enable */ +#define SAR_CFG_IDLE_CLP 0x10000000 /* SAR set CLP Bits of Null Cells */ +#define SAR_CFG_TX_FIFO_SIZE_1 0x04000000 /* TX FIFO Size = 1 cell */ +#define SAR_CFG_TX_FIFO_SIZE_2 0x08000000 /* TX FIFO Size = 2 cells */ +#define SAR_CFG_TX_FIFO_SIZE_4 0x0C000000 /* TX FIFO Size = 4 cells */ +#define SAR_CFG_TX_FIFO_SIZE_9 0x00000000 /* TX FIFO Size = 9 cells (full) */ +#define SAR_CFG_NO_IDLE 0x02000000 /* SAR sends no Null Cells */ +#define SAR_CFG_RSVD1 0x01000000 /* Reserved */ +#define SAR_CFG_RXSTQ_SIZE_2k 0x00000000 /* RX Stat Queue Size = 2048 byte */ +#define SAR_CFG_RXSTQ_SIZE_4k 0x00400000 /* RX Stat Queue Size = 4096 byte */ +#define SAR_CFG_RXSTQ_SIZE_8k 0x00800000 /* RX Stat Queue Size = 8192 byte */ +#define SAR_CFG_RXSTQ_SIZE_R 0x00C00000 /* RX Stat Queue Size = reserved */ +#define SAR_CFG_ICAPT 0x00200000 /* accept Invalid Cells */ +#define SAR_CFG_IGGFC 0x00100000 /* Ignore GFC */ +#define SAR_CFG_VPVCS_0 0x00000000 /* VPI/VCI Select bit range */ +#define SAR_CFG_VPVCS_1 0x00040000 /* VPI/VCI Select bit range */ +#define SAR_CFG_VPVCS_2 0x00080000 /* VPI/VCI Select bit range */ +#define SAR_CFG_VPVCS_8 0x000C0000 /* VPI/VCI Select bit range */ +#define SAR_CFG_CNTBL_1k 0x00000000 /* Connection Table Size */ +#define SAR_CFG_CNTBL_4k 0x00010000 /* Connection Table Size */ +#define SAR_CFG_CNTBL_16k 0x00020000 /* Connection Table Size */ +#define SAR_CFG_CNTBL_512 0x00030000 /* Connection Table Size */ +#define SAR_CFG_VPECA 0x00008000 /* VPI/VCI Error Cell Accept */ +#define SAR_CFG_RXINT_NOINT 0x00000000 /* No Interrupt on PDU received */ +#define SAR_CFG_RXINT_NODELAY 0x00001000 /* Interrupt without delay to host*/ +#define SAR_CFG_RXINT_256US 0x00002000 /* Interrupt with delay 256 usec */ +#define SAR_CFG_RXINT_505US 0x00003000 /* Interrupt with delay 505 usec */ +#define SAR_CFG_RXINT_742US 0x00004000 /* Interrupt with delay 742 usec */ +#define SAR_CFG_RAWIE 0x00000800 /* Raw Cell Queue Interrupt Enable*/ +#define SAR_CFG_RQFIE 0x00000400 /* RSQ Almost Full Int Enable */ +#define SAR_CFG_RSVD2 0x00000200 /* Reserved */ +#define SAR_CFG_CACHE 0x00000100 /* DMA on Cache Line Boundary */ +#define SAR_CFG_TMOIE 0x00000080 /* Timer Roll Over Int Enable */ +#define SAR_CFG_FBIE 0x00000040 /* Free Buffer Queue Int Enable */ +#define SAR_CFG_TXEN 0x00000020 /* Transmit Operation Enable */ +#define SAR_CFG_TXINT 0x00000010 /* Transmit status Int Enable */ +#define SAR_CFG_TXUIE 0x00000008 /* Transmit underrun Int Enable */ +#define SAR_CFG_UMODE 0x00000004 /* Utopia Mode Select */ +#define SAR_CFG_TXSFI 0x00000002 /* Transmit status Full Int Enable*/ +#define SAR_CFG_PHYIE 0x00000001 /* PHY Interrupt Enable */ + +#define SAR_CFG_TX_FIFO_SIZE_MASK 0x0C000000 /* TX FIFO Size Mask */ +#define SAR_CFG_RXSTQSIZE_MASK 0x00C00000 +#define SAR_CFG_CNTBL_MASK 0x00030000 +#define SAR_CFG_RXINT_MASK 0x00007000 + + +/*****************************************************************************/ +/* */ +/* Status Register bits */ +/* */ +/*****************************************************************************/ + +#define SAR_STAT_FRAC_3 0xF0000000 /* Fraction of Free Buffer Queue 3 */ +#define SAR_STAT_FRAC_2 0x0F000000 /* Fraction of Free Buffer Queue 2 */ +#define SAR_STAT_FRAC_1 0x00F00000 /* Fraction of Free Buffer Queue 1 */ +#define SAR_STAT_FRAC_0 0x000F0000 /* Fraction of Free Buffer Queue 0 */ +#define SAR_STAT_TSIF 0x00008000 /* Transmit Status Indicator */ +#define SAR_STAT_TXICP 0x00004000 /* Transmit Status Indicator */ +#define SAR_STAT_RSVD1 0x00002000 /* Reserved */ +#define SAR_STAT_TSQF 0x00001000 /* Transmit Status Queue full */ +#define SAR_STAT_TMROF 0x00000800 /* Timer overflow */ +#define SAR_STAT_PHYI 0x00000400 /* PHY device Interrupt flag */ +#define SAR_STAT_CMDBZ 0x00000200 /* ABR SAR Command Busy Flag */ +#define SAR_STAT_FBQ3A 0x00000100 /* Free Buffer Queue 3 Attention */ +#define SAR_STAT_FBQ2A 0x00000080 /* Free Buffer Queue 2 Attention */ +#define SAR_STAT_RSQF 0x00000040 /* Receive Status Queue full */ +#define SAR_STAT_EPDU 0x00000020 /* End Of PDU Flag */ +#define SAR_STAT_RAWCF 0x00000010 /* Raw Cell Flag */ +#define SAR_STAT_FBQ1A 0x00000008 /* Free Buffer Queue 1 Attention */ +#define SAR_STAT_FBQ0A 0x00000004 /* Free Buffer Queue 0 Attention */ +#define SAR_STAT_RSQAF 0x00000002 /* Receive Status Queue almost full*/ +#define SAR_STAT_RSVD2 0x00000001 /* Reserved */ + + +/*****************************************************************************/ +/* */ +/* General Purpose Register bits */ +/* */ +/*****************************************************************************/ + +#define SAR_GP_TXNCC_MASK 0xff000000 /* Transmit Negative Credit Count */ +#define SAR_GP_EEDI 0x00010000 /* EEPROM Data In */ +#define SAR_GP_BIGE 0x00008000 /* Big Endian Operation */ +#define SAR_GP_RM_NORMAL 0x00000000 /* Normal handling of RM cells */ +#define SAR_GP_RM_TO_RCQ 0x00002000 /* put RM cells into Raw Cell Queue */ +#define SAR_GP_RM_RSVD 0x00004000 /* Reserved */ +#define SAR_GP_RM_INHIBIT 0x00006000 /* Inhibit update of Connection tab */ +#define SAR_GP_PHY_RESET 0x00000008 /* PHY Reset */ +#define SAR_GP_EESCLK 0x00000004 /* EEPROM SCLK */ +#define SAR_GP_EECS 0x00000002 /* EEPROM Chip Select */ +#define SAR_GP_EEDO 0x00000001 /* EEPROM Data Out */ + + +/*****************************************************************************/ +/* */ +/* SAR local SRAM layout for 128k work SRAM */ +/* */ +/*****************************************************************************/ + +#define SAR_SRAM_SCD_SIZE 12 +#define SAR_SRAM_TCT_SIZE 8 +#define SAR_SRAM_RCT_SIZE 4 + +#define SAR_SRAM_TCT_128_BASE 0x00000 +#define SAR_SRAM_TCT_128_TOP 0x01fff +#define SAR_SRAM_RCT_128_BASE 0x02000 +#define SAR_SRAM_RCT_128_TOP 0x02fff +#define SAR_SRAM_FB0_128_BASE 0x03000 +#define SAR_SRAM_FB0_128_TOP 0x033ff +#define SAR_SRAM_FB1_128_BASE 0x03400 +#define SAR_SRAM_FB1_128_TOP 0x037ff +#define SAR_SRAM_FB2_128_BASE 0x03800 +#define SAR_SRAM_FB2_128_TOP 0x03bff +#define SAR_SRAM_FB3_128_BASE 0x03c00 +#define SAR_SRAM_FB3_128_TOP 0x03fff +#define SAR_SRAM_SCD_128_BASE 0x04000 +#define SAR_SRAM_SCD_128_TOP 0x07fff +#define SAR_SRAM_TST1_128_BASE 0x08000 +#define SAR_SRAM_TST1_128_TOP 0x0bfff +#define SAR_SRAM_TST2_128_BASE 0x0c000 +#define SAR_SRAM_TST2_128_TOP 0x0ffff +#define SAR_SRAM_ABRSTD_128_BASE 0x10000 +#define SAR_SRAM_ABRSTD_128_TOP 0x13fff +#define SAR_SRAM_RT_128_BASE 0x14000 +#define SAR_SRAM_RT_128_TOP 0x15fff + +#define SAR_SRAM_FIFO_128_BASE 0x18000 +#define SAR_SRAM_FIFO_128_TOP 0x1ffff + + +/*****************************************************************************/ +/* */ +/* SAR local SRAM layout for 32k work SRAM */ +/* */ +/*****************************************************************************/ + +#define SAR_SRAM_TCT_32_BASE 0x00000 +#define SAR_SRAM_TCT_32_TOP 0x00fff +#define SAR_SRAM_RCT_32_BASE 0x01000 +#define SAR_SRAM_RCT_32_TOP 0x017ff +#define SAR_SRAM_FB0_32_BASE 0x01800 +#define SAR_SRAM_FB0_32_TOP 0x01bff +#define SAR_SRAM_FB1_32_BASE 0x01c00 +#define SAR_SRAM_FB1_32_TOP 0x01fff +#define SAR_SRAM_FB2_32_BASE 0x02000 +#define SAR_SRAM_FB2_32_TOP 0x023ff +#define SAR_SRAM_FB3_32_BASE 0x02400 +#define SAR_SRAM_FB3_32_TOP 0x027ff +#define SAR_SRAM_SCD_32_BASE 0x02800 +#define SAR_SRAM_SCD_32_TOP 0x03fff +#define SAR_SRAM_TST1_32_BASE 0x04000 +#define SAR_SRAM_TST1_32_TOP 0x04fff +#define SAR_SRAM_TST2_32_BASE 0x05000 +#define SAR_SRAM_TST2_32_TOP 0x05fff +#define SAR_SRAM_ABRSTD_32_BASE 0x06000 +#define SAR_SRAM_ABRSTD_32_TOP 0x067ff +#define SAR_SRAM_RT_32_BASE 0x06800 +#define SAR_SRAM_RT_32_TOP 0x06fff +#define SAR_SRAM_FIFO_32_BASE 0x07000 +#define SAR_SRAM_FIFO_32_TOP 0x07fff + + +/*****************************************************************************/ +/* */ +/* TSR - Transmit Status Request */ +/* */ +/*****************************************************************************/ + +#define SAR_TSR_TYPE_TSR 0x80000000 +#define SAR_TSR_TYPE_TBD 0x00000000 +#define SAR_TSR_TSIF 0x20000000 +#define SAR_TSR_TAG_MASK 0x01F00000 + + +/*****************************************************************************/ +/* */ +/* TBD - Transmit Buffer Descriptor */ +/* */ +/*****************************************************************************/ + +#define SAR_TBD_EPDU 0x40000000 +#define SAR_TBD_TSIF 0x20000000 +#define SAR_TBD_OAM 0x10000000 +#define SAR_TBD_AAL0 0x00000000 +#define SAR_TBD_AAL34 0x04000000 +#define SAR_TBD_AAL5 0x08000000 +#define SAR_TBD_GTSI 0x02000000 +#define SAR_TBD_TAG_MASK 0x01F00000 + +#define SAR_TBD_VPI_MASK 0x0FF00000 +#define SAR_TBD_VCI_MASK 0x000FFFF0 +#define SAR_TBD_VC_MASK (SAR_TBD_VPI_MASK | SAR_TBD_VCI_MASK) + +#define SAR_TBD_VPI_SHIFT 20 +#define SAR_TBD_VCI_SHIFT 4 + + +/*****************************************************************************/ +/* */ +/* RXFD - Receive FIFO Descriptor */ +/* */ +/*****************************************************************************/ + +#define SAR_RXFD_SIZE_MASK 0x0F000000 +#define SAR_RXFD_SIZE_512 0x00000000 /* 512 words */ +#define SAR_RXFD_SIZE_1K 0x01000000 /* 1k words */ +#define SAR_RXFD_SIZE_2K 0x02000000 /* 2k words */ +#define SAR_RXFD_SIZE_4K 0x03000000 /* 4k words */ +#define SAR_RXFD_SIZE_8K 0x04000000 /* 8k words */ +#define SAR_RXFD_SIZE_16K 0x05000000 /* 16k words */ +#define SAR_RXFD_SIZE_32K 0x06000000 /* 32k words */ +#define SAR_RXFD_SIZE_64K 0x07000000 /* 64k words */ +#define SAR_RXFD_SIZE_128K 0x08000000 /* 128k words */ +#define SAR_RXFD_SIZE_256K 0x09000000 /* 256k words */ +#define SAR_RXFD_ADDR_MASK 0x001ffc00 + + +/*****************************************************************************/ +/* */ +/* ABRSTD - ABR + VBR Schedule Tables */ +/* */ +/*****************************************************************************/ + +#define SAR_ABRSTD_SIZE_MASK 0x07000000 +#define SAR_ABRSTD_SIZE_512 0x00000000 /* 512 words */ +#define SAR_ABRSTD_SIZE_1K 0x01000000 /* 1k words */ +#define SAR_ABRSTD_SIZE_2K 0x02000000 /* 2k words */ +#define SAR_ABRSTD_SIZE_4K 0x03000000 /* 4k words */ +#define SAR_ABRSTD_SIZE_8K 0x04000000 /* 8k words */ +#define SAR_ABRSTD_SIZE_16K 0x05000000 /* 16k words */ +#define SAR_ABRSTD_ADDR_MASK 0x001ffc00 + + +/*****************************************************************************/ +/* */ +/* RCTE - Receive Connection Table Entry */ +/* */ +/*****************************************************************************/ + +#define SAR_RCTE_IL_MASK 0xE0000000 /* inactivity limit */ +#define SAR_RCTE_IC_MASK 0x1C000000 /* inactivity count */ +#define SAR_RCTE_RSVD 0x02000000 /* reserved */ +#define SAR_RCTE_LCD 0x01000000 /* last cell data */ +#define SAR_RCTE_CI_VC 0x00800000 /* EFCI in previous cell of VC */ +#define SAR_RCTE_FBP_01 0x00000000 /* 1. cell->FBQ0, others->FBQ1 */ +#define SAR_RCTE_FBP_1 0x00200000 /* use FBQ 1 for all cells */ +#define SAR_RCTE_FBP_2 0x00400000 /* use FBQ 2 for all cells */ +#define SAR_RCTE_FBP_3 0x00600000 /* use FBQ 3 for all cells */ +#define SAR_RCTE_NZ_GFC 0x00100000 /* non zero GFC in all cell of VC */ +#define SAR_RCTE_CONNECTOPEN 0x00080000 /* VC is open */ +#define SAR_RCTE_AAL_MASK 0x00070000 /* mask for AAL type field s.b. */ +#define SAR_RCTE_RAWCELLINTEN 0x00008000 /* raw cell interrupt enable */ +#define SAR_RCTE_RXCONCELLADDR 0x00004000 /* RX constant cell address */ +#define SAR_RCTE_BUFFSTAT_MASK 0x00003000 /* buffer status */ +#define SAR_RCTE_EFCI 0x00000800 /* EFCI Congestion flag */ +#define SAR_RCTE_CLP 0x00000400 /* Cell Loss Priority flag */ +#define SAR_RCTE_CRC 0x00000200 /* Received CRC Error */ +#define SAR_RCTE_CELLCNT_MASK 0x000001FF /* cell Count */ + +#define SAR_RCTE_AAL0 0x00000000 /* AAL types for ALL field */ +#define SAR_RCTE_AAL34 0x00010000 +#define SAR_RCTE_AAL5 0x00020000 +#define SAR_RCTE_RCQ 0x00030000 +#define SAR_RCTE_OAM 0x00040000 + +#define TCMDQ_START 0x01000000 +#define TCMDQ_LACR 0x02000000 +#define TCMDQ_START_LACR 0x03000000 +#define TCMDQ_INIT_ER 0x04000000 +#define TCMDQ_HALT 0x05000000 + + +struct idt77252_skb_prv { + struct scqe tbd; /* Transmit Buffer Descriptor */ + dma_addr_t paddr; /* DMA handle */ + u32 pool; /* sb_pool handle */ +}; + +#define IDT77252_PRV_TBD(skb) \ + (((struct idt77252_skb_prv *)(ATM_SKB(skb)+1))->tbd) +#define IDT77252_PRV_PADDR(skb) \ + (((struct idt77252_skb_prv *)(ATM_SKB(skb)+1))->paddr) +#define IDT77252_PRV_POOL(skb) \ + (((struct idt77252_skb_prv *)(ATM_SKB(skb)+1))->pool) + +/*****************************************************************************/ +/* */ +/* PCI related items */ +/* */ +/*****************************************************************************/ + +#ifndef PCI_VENDOR_ID_IDT +#define PCI_VENDOR_ID_IDT 0x111D +#endif /* PCI_VENDOR_ID_IDT */ + +#ifndef PCI_DEVICE_ID_IDT_IDT77252 +#define PCI_DEVICE_ID_IDT_IDT77252 0x0003 +#endif /* PCI_DEVICE_ID_IDT_IDT772052 */ + + +#endif /* !(_IDT77252_H) */ diff --git a/drivers/atm/idt77252_tables.h b/drivers/atm/idt77252_tables.h new file mode 100644 index 000000000..12b81e046 --- /dev/null +++ b/drivers/atm/idt77252_tables.h @@ -0,0 +1,781 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Do not edit, automatically generated by `./genrtbl'. + * + * Cell Line Rate: 353207.55 (155520000 bps) + */ + +static unsigned int log_to_rate[] = +{ +/* 000 */ 0x8d022e27, /* cps = 10.02, nrm = 3, interval = 35264.00 */ +/* 001 */ 0x8d362e11, /* cps = 10.42, nrm = 3, interval = 33856.00 */ +/* 002 */ 0x8d6e2bf8, /* cps = 10.86, nrm = 3, interval = 32512.00 */ +/* 003 */ 0x8da82bcf, /* cps = 11.31, nrm = 3, interval = 31200.00 */ +/* 004 */ 0x8de42ba8, /* cps = 11.78, nrm = 3, interval = 29952.00 */ +/* 005 */ 0x8e242b82, /* cps = 12.28, nrm = 3, interval = 28736.00 */ +/* 006 */ 0x8e662b5e, /* cps = 12.80, nrm = 3, interval = 27584.00 */ +/* 007 */ 0x8eaa2b3c, /* cps = 13.33, nrm = 3, interval = 26496.00 */ +/* 008 */ 0x8ef22b1a, /* cps = 13.89, nrm = 3, interval = 25408.00 */ +/* 009 */ 0x8f3e2afa, /* cps = 14.48, nrm = 3, interval = 24384.00 */ +/* 010 */ 0x8f8a2adc, /* cps = 15.08, nrm = 3, interval = 23424.00 */ +/* 011 */ 0x8fdc2abe, /* cps = 15.72, nrm = 3, interval = 22464.00 */ +/* 012 */ 0x90182aa2, /* cps = 16.38, nrm = 3, interval = 21568.00 */ +/* 013 */ 0x90422a87, /* cps = 17.03, nrm = 3, interval = 20704.00 */ +/* 014 */ 0x90702a6d, /* cps = 17.75, nrm = 3, interval = 19872.00 */ +/* 015 */ 0x90a02a54, /* cps = 18.50, nrm = 3, interval = 19072.00 */ +/* 016 */ 0x90d22a3c, /* cps = 19.28, nrm = 3, interval = 18304.00 */ +/* 017 */ 0x91062a25, /* cps = 20.09, nrm = 3, interval = 17568.00 */ +/* 018 */ 0x913c2a0f, /* cps = 20.94, nrm = 3, interval = 16864.00 */ +/* 019 */ 0x917427f3, /* cps = 21.81, nrm = 3, interval = 16176.00 */ +/* 020 */ 0x91b027ca, /* cps = 22.75, nrm = 3, interval = 15520.00 */ +/* 021 */ 0x91ec27a3, /* cps = 23.69, nrm = 3, interval = 14896.00 */ +/* 022 */ 0x922c277e, /* cps = 24.69, nrm = 3, interval = 14304.00 */ +/* 023 */ 0x926e275a, /* cps = 25.72, nrm = 3, interval = 13728.00 */ +/* 024 */ 0x92b42737, /* cps = 26.81, nrm = 3, interval = 13168.00 */ +/* 025 */ 0x92fc2716, /* cps = 27.94, nrm = 3, interval = 12640.00 */ +/* 026 */ 0x934626f6, /* cps = 29.09, nrm = 3, interval = 12128.00 */ +/* 027 */ 0x939426d8, /* cps = 30.31, nrm = 3, interval = 11648.00 */ +/* 028 */ 0x93e426bb, /* cps = 31.56, nrm = 3, interval = 11184.00 */ +/* 029 */ 0x941e269e, /* cps = 32.94, nrm = 3, interval = 10720.00 */ +/* 030 */ 0x944a2683, /* cps = 34.31, nrm = 3, interval = 10288.00 */ +/* 031 */ 0x9476266a, /* cps = 35.69, nrm = 3, interval = 9888.00 */ +/* 032 */ 0x94a62651, /* cps = 37.19, nrm = 3, interval = 9488.00 */ +/* 033 */ 0x94d82639, /* cps = 38.75, nrm = 3, interval = 9104.00 */ +/* 034 */ 0x950c6622, /* cps = 40.38, nrm = 4, interval = 8736.00 */ +/* 035 */ 0x9544660c, /* cps = 42.12, nrm = 4, interval = 8384.00 */ +/* 036 */ 0x957c63ee, /* cps = 43.88, nrm = 4, interval = 8048.00 */ +/* 037 */ 0x95b663c6, /* cps = 45.69, nrm = 4, interval = 7728.00 */ +/* 038 */ 0x95f4639f, /* cps = 47.62, nrm = 4, interval = 7416.00 */ +/* 039 */ 0x96346379, /* cps = 49.62, nrm = 4, interval = 7112.00 */ +/* 040 */ 0x96766356, /* cps = 51.69, nrm = 4, interval = 6832.00 */ +/* 041 */ 0x96bc6333, /* cps = 53.88, nrm = 4, interval = 6552.00 */ +/* 042 */ 0x97046312, /* cps = 56.12, nrm = 4, interval = 6288.00 */ +/* 043 */ 0x974e62f3, /* cps = 58.44, nrm = 4, interval = 6040.00 */ +/* 044 */ 0x979e62d4, /* cps = 60.94, nrm = 4, interval = 5792.00 */ +/* 045 */ 0x97f062b7, /* cps = 63.50, nrm = 4, interval = 5560.00 */ +/* 046 */ 0x9822629b, /* cps = 66.12, nrm = 4, interval = 5336.00 */ +/* 047 */ 0x984e6280, /* cps = 68.88, nrm = 4, interval = 5120.00 */ +/* 048 */ 0x987e6266, /* cps = 71.88, nrm = 4, interval = 4912.00 */ +/* 049 */ 0x98ac624e, /* cps = 74.75, nrm = 4, interval = 4720.00 */ +/* 050 */ 0x98e06236, /* cps = 78.00, nrm = 4, interval = 4528.00 */ +/* 051 */ 0x9914a21f, /* cps = 81.25, nrm = 8, interval = 4344.00 */ +/* 052 */ 0x994aa209, /* cps = 84.62, nrm = 8, interval = 4168.00 */ +/* 053 */ 0x99829fe9, /* cps = 88.12, nrm = 8, interval = 4004.00 */ +/* 054 */ 0x99be9fc1, /* cps = 91.88, nrm = 8, interval = 3844.00 */ +/* 055 */ 0x99fc9f9a, /* cps = 95.75, nrm = 8, interval = 3688.00 */ +/* 056 */ 0x9a3c9f75, /* cps = 99.75, nrm = 8, interval = 3540.00 */ +/* 057 */ 0x9a809f51, /* cps = 104.00, nrm = 8, interval = 3396.00 */ +/* 058 */ 0x9ac49f2f, /* cps = 108.25, nrm = 8, interval = 3260.00 */ +/* 059 */ 0x9b0e9f0e, /* cps = 112.88, nrm = 8, interval = 3128.00 */ +/* 060 */ 0x9b589eef, /* cps = 117.50, nrm = 8, interval = 3004.00 */ +/* 061 */ 0x9ba69ed1, /* cps = 122.38, nrm = 8, interval = 2884.00 */ +/* 062 */ 0x9bf89eb4, /* cps = 127.50, nrm = 8, interval = 2768.00 */ +/* 063 */ 0x9c269e98, /* cps = 132.75, nrm = 8, interval = 2656.00 */ +/* 064 */ 0x9c549e7d, /* cps = 138.50, nrm = 8, interval = 2548.00 */ +/* 065 */ 0x9c849e63, /* cps = 144.50, nrm = 8, interval = 2444.00 */ +/* 066 */ 0x9cb29e4b, /* cps = 150.25, nrm = 8, interval = 2348.00 */ +/* 067 */ 0x9ce69e33, /* cps = 156.75, nrm = 8, interval = 2252.00 */ +/* 068 */ 0x9d1cde1c, /* cps = 163.50, nrm = 16, interval = 2160.00 */ +/* 069 */ 0x9d50de07, /* cps = 170.00, nrm = 16, interval = 2076.00 */ +/* 070 */ 0x9d8adbe4, /* cps = 177.25, nrm = 16, interval = 1992.00 */ +/* 071 */ 0x9dc4dbbc, /* cps = 184.50, nrm = 16, interval = 1912.00 */ +/* 072 */ 0x9e02db96, /* cps = 192.25, nrm = 16, interval = 1836.00 */ +/* 073 */ 0x9e42db71, /* cps = 200.25, nrm = 16, interval = 1762.00 */ +/* 074 */ 0x9e86db4d, /* cps = 208.75, nrm = 16, interval = 1690.00 */ +/* 075 */ 0x9ecedb2b, /* cps = 217.75, nrm = 16, interval = 1622.00 */ +/* 076 */ 0x9f16db0a, /* cps = 226.75, nrm = 16, interval = 1556.00 */ +/* 077 */ 0x9f62daeb, /* cps = 236.25, nrm = 16, interval = 1494.00 */ +/* 078 */ 0x9fb2dacd, /* cps = 246.25, nrm = 16, interval = 1434.00 */ +/* 079 */ 0xa002dab0, /* cps = 256.50, nrm = 16, interval = 1376.00 */ +/* 080 */ 0xa02eda94, /* cps = 267.50, nrm = 16, interval = 1320.00 */ +/* 081 */ 0xa05ada7a, /* cps = 278.50, nrm = 16, interval = 1268.00 */ +/* 082 */ 0xa088da60, /* cps = 290.00, nrm = 16, interval = 1216.00 */ +/* 083 */ 0xa0b8da48, /* cps = 302.00, nrm = 16, interval = 1168.00 */ +/* 084 */ 0xa0ecda30, /* cps = 315.00, nrm = 16, interval = 1120.00 */ +/* 085 */ 0xa1211a1a, /* cps = 328.00, nrm = 32, interval = 1076.00 */ +/* 086 */ 0xa1591a04, /* cps = 342.00, nrm = 32, interval = 1032.00 */ +/* 087 */ 0xa19117df, /* cps = 356.00, nrm = 32, interval = 991.00 */ +/* 088 */ 0xa1cd17b7, /* cps = 371.00, nrm = 32, interval = 951.00 */ +/* 089 */ 0xa20b1791, /* cps = 386.50, nrm = 32, interval = 913.00 */ +/* 090 */ 0xa24d176c, /* cps = 403.00, nrm = 32, interval = 876.00 */ +/* 091 */ 0xa28f1749, /* cps = 419.50, nrm = 32, interval = 841.00 */ +/* 092 */ 0xa2d71727, /* cps = 437.50, nrm = 32, interval = 807.00 */ +/* 093 */ 0xa31f1707, /* cps = 455.50, nrm = 32, interval = 775.00 */ +/* 094 */ 0xa36d16e7, /* cps = 475.00, nrm = 32, interval = 743.00 */ +/* 095 */ 0xa3bd16c9, /* cps = 495.00, nrm = 32, interval = 713.00 */ +/* 096 */ 0xa40716ad, /* cps = 515.00, nrm = 32, interval = 685.00 */ +/* 097 */ 0xa4331691, /* cps = 537.00, nrm = 32, interval = 657.00 */ +/* 098 */ 0xa45f1677, /* cps = 559.00, nrm = 32, interval = 631.00 */ +/* 099 */ 0xa48f165d, /* cps = 583.00, nrm = 32, interval = 605.00 */ +/* 100 */ 0xa4bf1645, /* cps = 607.00, nrm = 32, interval = 581.00 */ +/* 101 */ 0xa4f1162e, /* cps = 632.00, nrm = 32, interval = 558.00 */ +/* 102 */ 0xa5291617, /* cps = 660.00, nrm = 32, interval = 535.00 */ +/* 103 */ 0xa55f1602, /* cps = 687.00, nrm = 32, interval = 514.00 */ +/* 104 */ 0xa59913da, /* cps = 716.00, nrm = 32, interval = 493.00 */ +/* 105 */ 0xa5d513b2, /* cps = 746.00, nrm = 32, interval = 473.00 */ +/* 106 */ 0xa613138c, /* cps = 777.00, nrm = 32, interval = 454.00 */ +/* 107 */ 0xa6551368, /* cps = 810.00, nrm = 32, interval = 436.00 */ +/* 108 */ 0xa6971345, /* cps = 843.00, nrm = 32, interval = 418.50 */ +/* 109 */ 0xa6df1323, /* cps = 879.00, nrm = 32, interval = 401.50 */ +/* 110 */ 0xa7291303, /* cps = 916.00, nrm = 32, interval = 385.50 */ +/* 111 */ 0xa77512e4, /* cps = 954.00, nrm = 32, interval = 370.00 */ +/* 112 */ 0xa7c512c6, /* cps = 994.00, nrm = 32, interval = 355.00 */ +/* 113 */ 0xa80d12a9, /* cps = 1036.00, nrm = 32, interval = 340.50 */ +/* 114 */ 0xa839128e, /* cps = 1080.00, nrm = 32, interval = 327.00 */ +/* 115 */ 0xa8651274, /* cps = 1124.00, nrm = 32, interval = 314.00 */ +/* 116 */ 0xa895125a, /* cps = 1172.00, nrm = 32, interval = 301.00 */ +/* 117 */ 0xa8c71242, /* cps = 1222.00, nrm = 32, interval = 289.00 */ +/* 118 */ 0xa8f9122b, /* cps = 1272.00, nrm = 32, interval = 277.50 */ +/* 119 */ 0xa92f1214, /* cps = 1326.00, nrm = 32, interval = 266.00 */ +/* 120 */ 0xa9670ffe, /* cps = 1382.00, nrm = 32, interval = 255.50 */ +/* 121 */ 0xa9a10fd5, /* cps = 1440.00, nrm = 32, interval = 245.25 */ +/* 122 */ 0xa9db0fae, /* cps = 1498.00, nrm = 32, interval = 235.50 */ +/* 123 */ 0xaa1b0f88, /* cps = 1562.00, nrm = 32, interval = 226.00 */ +/* 124 */ 0xaa5d0f63, /* cps = 1628.00, nrm = 32, interval = 216.75 */ +/* 125 */ 0xaaa10f41, /* cps = 1696.00, nrm = 32, interval = 208.25 */ +/* 126 */ 0xaae90f1f, /* cps = 1768.00, nrm = 32, interval = 199.75 */ +/* 127 */ 0xab330eff, /* cps = 1842.00, nrm = 32, interval = 191.75 */ +/* 128 */ 0xab7f0ee0, /* cps = 1918.00, nrm = 32, interval = 184.00 */ +/* 129 */ 0xabd10ec2, /* cps = 2000.00, nrm = 32, interval = 176.50 */ +/* 130 */ 0xac110ea6, /* cps = 2080.00, nrm = 32, interval = 169.50 */ +/* 131 */ 0xac3d0e8b, /* cps = 2168.00, nrm = 32, interval = 162.75 */ +/* 132 */ 0xac6d0e70, /* cps = 2264.00, nrm = 32, interval = 156.00 */ +/* 133 */ 0xac9b0e57, /* cps = 2356.00, nrm = 32, interval = 149.75 */ +/* 134 */ 0xaccd0e3f, /* cps = 2456.00, nrm = 32, interval = 143.75 */ +/* 135 */ 0xacff0e28, /* cps = 2556.00, nrm = 32, interval = 138.00 */ +/* 136 */ 0xad350e12, /* cps = 2664.00, nrm = 32, interval = 132.50 */ +/* 137 */ 0xad6d0bf9, /* cps = 2776.00, nrm = 32, interval = 127.12 */ +/* 138 */ 0xada70bd0, /* cps = 2892.00, nrm = 32, interval = 122.00 */ +/* 139 */ 0xade30ba9, /* cps = 3012.00, nrm = 32, interval = 117.12 */ +/* 140 */ 0xae230b83, /* cps = 3140.00, nrm = 32, interval = 112.38 */ +/* 141 */ 0xae650b5f, /* cps = 3272.00, nrm = 32, interval = 107.88 */ +/* 142 */ 0xaeab0b3c, /* cps = 3412.00, nrm = 32, interval = 103.50 */ +/* 143 */ 0xaef10b1b, /* cps = 3552.00, nrm = 32, interval = 99.38 */ +/* 144 */ 0xaf3b0afb, /* cps = 3700.00, nrm = 32, interval = 95.38 */ +/* 145 */ 0xaf8b0adc, /* cps = 3860.00, nrm = 32, interval = 91.50 */ +/* 146 */ 0xafd90abf, /* cps = 4016.00, nrm = 32, interval = 87.88 */ +/* 147 */ 0xb0170aa3, /* cps = 4184.00, nrm = 32, interval = 84.38 */ +/* 148 */ 0xb0430a87, /* cps = 4360.00, nrm = 32, interval = 80.88 */ +/* 149 */ 0xb0710a6d, /* cps = 4544.00, nrm = 32, interval = 77.62 */ +/* 150 */ 0xb0a10a54, /* cps = 4736.00, nrm = 32, interval = 74.50 */ +/* 151 */ 0xb0d30a3c, /* cps = 4936.00, nrm = 32, interval = 71.50 */ +/* 152 */ 0xb1070a25, /* cps = 5144.00, nrm = 32, interval = 68.62 */ +/* 153 */ 0xb13d0a0f, /* cps = 5360.00, nrm = 32, interval = 65.88 */ +/* 154 */ 0xb17507f4, /* cps = 5584.00, nrm = 32, interval = 63.25 */ +/* 155 */ 0xb1af07cb, /* cps = 5816.00, nrm = 32, interval = 60.69 */ +/* 156 */ 0xb1eb07a4, /* cps = 6056.00, nrm = 32, interval = 58.25 */ +/* 157 */ 0xb22b077f, /* cps = 6312.00, nrm = 32, interval = 55.94 */ +/* 158 */ 0xb26d075b, /* cps = 6576.00, nrm = 32, interval = 53.69 */ +/* 159 */ 0xb2b30738, /* cps = 6856.00, nrm = 32, interval = 51.50 */ +/* 160 */ 0xb2fb0717, /* cps = 7144.00, nrm = 32, interval = 49.44 */ +/* 161 */ 0xb34506f7, /* cps = 7440.00, nrm = 32, interval = 47.44 */ +/* 162 */ 0xb39306d9, /* cps = 7752.00, nrm = 32, interval = 45.56 */ +/* 163 */ 0xb3e506bb, /* cps = 8080.00, nrm = 32, interval = 43.69 */ +/* 164 */ 0xb41d069f, /* cps = 8416.00, nrm = 32, interval = 41.94 */ +/* 165 */ 0xb4490684, /* cps = 8768.00, nrm = 32, interval = 40.25 */ +/* 166 */ 0xb477066a, /* cps = 9136.00, nrm = 32, interval = 38.62 */ +/* 167 */ 0xb4a70651, /* cps = 9520.00, nrm = 32, interval = 37.06 */ +/* 168 */ 0xb4d90639, /* cps = 9920.00, nrm = 32, interval = 35.56 */ +/* 169 */ 0xb50d0622, /* cps = 10336.00, nrm = 32, interval = 34.12 */ +/* 170 */ 0xb545060c, /* cps = 10784.00, nrm = 32, interval = 32.75 */ +/* 171 */ 0xb57b03ef, /* cps = 11216.00, nrm = 32, interval = 31.47 */ +/* 172 */ 0xb5b503c7, /* cps = 11680.00, nrm = 32, interval = 30.22 */ +/* 173 */ 0xb5f303a0, /* cps = 12176.00, nrm = 32, interval = 29.00 */ +/* 174 */ 0xb633037a, /* cps = 12688.00, nrm = 32, interval = 27.81 */ +/* 175 */ 0xb6750357, /* cps = 13216.00, nrm = 32, interval = 26.72 */ +/* 176 */ 0xb6bb0334, /* cps = 13776.00, nrm = 32, interval = 25.62 */ +/* 177 */ 0xb7030313, /* cps = 14352.00, nrm = 32, interval = 24.59 */ +/* 178 */ 0xb74f02f3, /* cps = 14960.00, nrm = 32, interval = 23.59 */ +/* 179 */ 0xb79d02d5, /* cps = 15584.00, nrm = 32, interval = 22.66 */ +/* 180 */ 0xb7ed02b8, /* cps = 16224.00, nrm = 32, interval = 21.75 */ +/* 181 */ 0xb821029c, /* cps = 16896.00, nrm = 32, interval = 20.88 */ +/* 182 */ 0xb84f0281, /* cps = 17632.00, nrm = 32, interval = 20.03 */ +/* 183 */ 0xb87d0267, /* cps = 18368.00, nrm = 32, interval = 19.22 */ +/* 184 */ 0xb8ad024e, /* cps = 19136.00, nrm = 32, interval = 18.44 */ +/* 185 */ 0xb8dd0237, /* cps = 19904.00, nrm = 32, interval = 17.72 */ +/* 186 */ 0xb9130220, /* cps = 20768.00, nrm = 32, interval = 17.00 */ +/* 187 */ 0xb949020a, /* cps = 21632.00, nrm = 32, interval = 16.31 */ +/* 188 */ 0xb98301f5, /* cps = 22560.00, nrm = 32, interval = 15.66 */ +/* 189 */ 0xb9bd01e1, /* cps = 23488.00, nrm = 32, interval = 15.03 */ +/* 190 */ 0xb9fd01cd, /* cps = 24512.00, nrm = 32, interval = 14.41 */ +/* 191 */ 0xba3b01bb, /* cps = 25504.00, nrm = 32, interval = 13.84 */ +/* 192 */ 0xba7f01a9, /* cps = 26592.00, nrm = 32, interval = 13.28 */ +/* 193 */ 0xbac30198, /* cps = 27680.00, nrm = 32, interval = 12.75 */ +/* 194 */ 0xbb0f0187, /* cps = 28896.00, nrm = 32, interval = 12.22 */ +/* 195 */ 0xbb570178, /* cps = 30048.00, nrm = 32, interval = 11.75 */ +/* 196 */ 0xbbab0168, /* cps = 31392.00, nrm = 32, interval = 11.25 */ +/* 197 */ 0xbbf9015a, /* cps = 32640.00, nrm = 32, interval = 10.81 */ +/* 198 */ 0xbc27014c, /* cps = 33984.00, nrm = 32, interval = 10.38 */ +/* 199 */ 0xbc53013f, /* cps = 35392.00, nrm = 32, interval = 9.97 */ +/* 200 */ 0xbc830132, /* cps = 36928.00, nrm = 32, interval = 9.56 */ +/* 201 */ 0xbcb50125, /* cps = 38528.00, nrm = 32, interval = 9.16 */ +/* 202 */ 0xbce5011a, /* cps = 40064.00, nrm = 32, interval = 8.81 */ +/* 203 */ 0xbd1d010e, /* cps = 41856.00, nrm = 32, interval = 8.44 */ +/* 204 */ 0xbd530103, /* cps = 43584.00, nrm = 32, interval = 8.09 */ +/* 205 */ 0xbd8b00f9, /* cps = 45376.00, nrm = 32, interval = 7.78 */ +/* 206 */ 0xbdc500ef, /* cps = 47232.00, nrm = 32, interval = 7.47 */ +/* 207 */ 0xbe0700e5, /* cps = 49344.00, nrm = 32, interval = 7.16 */ +/* 208 */ 0xbe4500dc, /* cps = 51328.00, nrm = 32, interval = 6.88 */ +/* 209 */ 0xbe8900d3, /* cps = 53504.00, nrm = 32, interval = 6.59 */ +/* 210 */ 0xbecb00cb, /* cps = 55616.00, nrm = 32, interval = 6.34 */ +/* 211 */ 0xbf1d00c2, /* cps = 58240.00, nrm = 32, interval = 6.06 */ +/* 212 */ 0xbf6100bb, /* cps = 60416.00, nrm = 32, interval = 5.84 */ +/* 213 */ 0xbfb500b3, /* cps = 63104.00, nrm = 32, interval = 5.59 */ +/* 214 */ 0xc00300ac, /* cps = 65664.00, nrm = 32, interval = 5.38 */ +/* 215 */ 0xc02f00a5, /* cps = 68480.00, nrm = 32, interval = 5.16 */ +/* 216 */ 0xc05d009e, /* cps = 71424.00, nrm = 32, interval = 4.94 */ +/* 217 */ 0xc0890098, /* cps = 74240.00, nrm = 32, interval = 4.75 */ +/* 218 */ 0xc0b90092, /* cps = 77312.00, nrm = 32, interval = 4.56 */ +/* 219 */ 0xc0ed008c, /* cps = 80640.00, nrm = 32, interval = 4.38 */ +/* 220 */ 0xc1250086, /* cps = 84224.00, nrm = 32, interval = 4.19 */ +/* 221 */ 0xc1590081, /* cps = 87552.00, nrm = 32, interval = 4.03 */ +/* 222 */ 0xc191007c, /* cps = 91136.00, nrm = 32, interval = 3.88 */ +/* 223 */ 0xc1cd0077, /* cps = 94976.00, nrm = 32, interval = 3.72 */ +/* 224 */ 0xc20d0072, /* cps = 99072.00, nrm = 32, interval = 3.56 */ +/* 225 */ 0xc255006d, /* cps = 103680.00, nrm = 32, interval = 3.41 */ +/* 226 */ 0xc2910069, /* cps = 107520.00, nrm = 32, interval = 3.28 */ +/* 227 */ 0xc2d50065, /* cps = 111872.00, nrm = 32, interval = 3.16 */ +/* 228 */ 0xc32f0060, /* cps = 117632.00, nrm = 32, interval = 3.00 */ +/* 229 */ 0xc36b005d, /* cps = 121472.00, nrm = 32, interval = 2.91 */ +/* 230 */ 0xc3c10059, /* cps = 126976.00, nrm = 32, interval = 2.78 */ +/* 231 */ 0xc40f0055, /* cps = 132864.00, nrm = 32, interval = 2.66 */ +/* 232 */ 0xc4350052, /* cps = 137728.00, nrm = 32, interval = 2.56 */ +/* 233 */ 0xc46d004e, /* cps = 144896.00, nrm = 32, interval = 2.44 */ +/* 234 */ 0xc499004b, /* cps = 150528.00, nrm = 32, interval = 2.34 */ +/* 235 */ 0xc4cb0048, /* cps = 156928.00, nrm = 32, interval = 2.25 */ +/* 236 */ 0xc4ff0045, /* cps = 163584.00, nrm = 32, interval = 2.16 */ +/* 237 */ 0xc5250043, /* cps = 168448.00, nrm = 32, interval = 2.09 */ +/* 238 */ 0xc5630040, /* cps = 176384.00, nrm = 32, interval = 2.00 */ +/* 239 */ 0xc5a7003d, /* cps = 185088.00, nrm = 32, interval = 1.91 */ +/* 240 */ 0xc5d9003b, /* cps = 191488.00, nrm = 32, interval = 1.84 */ +/* 241 */ 0xc6290038, /* cps = 201728.00, nrm = 32, interval = 1.75 */ +/* 242 */ 0xc6630036, /* cps = 209152.00, nrm = 32, interval = 1.69 */ +/* 243 */ 0xc6a30034, /* cps = 217344.00, nrm = 32, interval = 1.62 */ +/* 244 */ 0xc6e70032, /* cps = 226048.00, nrm = 32, interval = 1.56 */ +/* 245 */ 0xc72f0030, /* cps = 235264.00, nrm = 32, interval = 1.50 */ +/* 246 */ 0xc77f002e, /* cps = 245504.00, nrm = 32, interval = 1.44 */ +/* 247 */ 0xc7d7002c, /* cps = 256768.00, nrm = 32, interval = 1.38 */ +/* 248 */ 0xc81b002a, /* cps = 268800.00, nrm = 32, interval = 1.31 */ +/* 249 */ 0xc84f0028, /* cps = 282112.00, nrm = 32, interval = 1.25 */ +/* 250 */ 0xc86d0027, /* cps = 289792.00, nrm = 32, interval = 1.22 */ +/* 251 */ 0xc8a90025, /* cps = 305152.00, nrm = 32, interval = 1.16 */ +/* 252 */ 0xc8cb0024, /* cps = 313856.00, nrm = 32, interval = 1.12 */ +/* 253 */ 0xc9130022, /* cps = 332288.00, nrm = 32, interval = 1.06 */ +/* 254 */ 0xc9390021, /* cps = 342016.00, nrm = 32, interval = 1.03 */ +/* 255 */ 0xc9630020, /* cps = 352768.00, nrm = 32, interval = 1.00 */ +}; + +static unsigned char rate_to_log[] = +{ +/* 1.00 => 0 */ 0x00, /* => 10.02 */ +/* 1.06 => 0 */ 0x00, /* => 10.02 */ +/* 1.12 => 0 */ 0x00, /* => 10.02 */ +/* 1.19 => 0 */ 0x00, /* => 10.02 */ +/* 1.25 => 0 */ 0x00, /* => 10.02 */ +/* 1.31 => 0 */ 0x00, /* => 10.02 */ +/* 1.38 => 0 */ 0x00, /* => 10.02 */ +/* 1.44 => 0 */ 0x00, /* => 10.02 */ +/* 1.50 => 0 */ 0x00, /* => 10.02 */ +/* 1.56 => 0 */ 0x00, /* => 10.02 */ +/* 1.62 => 0 */ 0x00, /* => 10.02 */ +/* 1.69 => 0 */ 0x00, /* => 10.02 */ +/* 1.75 => 0 */ 0x00, /* => 10.02 */ +/* 1.81 => 0 */ 0x00, /* => 10.02 */ +/* 1.88 => 0 */ 0x00, /* => 10.02 */ +/* 1.94 => 0 */ 0x00, /* => 10.02 */ +/* 2.00 => 0 */ 0x00, /* => 10.02 */ +/* 2.12 => 0 */ 0x00, /* => 10.02 */ +/* 2.25 => 0 */ 0x00, /* => 10.02 */ +/* 2.38 => 0 */ 0x00, /* => 10.02 */ +/* 2.50 => 0 */ 0x00, /* => 10.02 */ +/* 2.62 => 0 */ 0x00, /* => 10.02 */ +/* 2.75 => 0 */ 0x00, /* => 10.02 */ +/* 2.88 => 0 */ 0x00, /* => 10.02 */ +/* 3.00 => 0 */ 0x00, /* => 10.02 */ +/* 3.12 => 0 */ 0x00, /* => 10.02 */ +/* 3.25 => 0 */ 0x00, /* => 10.02 */ +/* 3.38 => 0 */ 0x00, /* => 10.02 */ +/* 3.50 => 0 */ 0x00, /* => 10.02 */ +/* 3.62 => 0 */ 0x00, /* => 10.02 */ +/* 3.75 => 0 */ 0x00, /* => 10.02 */ +/* 3.88 => 0 */ 0x00, /* => 10.02 */ +/* 4.00 => 0 */ 0x00, /* => 10.02 */ +/* 4.25 => 0 */ 0x00, /* => 10.02 */ +/* 4.50 => 0 */ 0x00, /* => 10.02 */ +/* 4.75 => 0 */ 0x00, /* => 10.02 */ +/* 5.00 => 0 */ 0x00, /* => 10.02 */ +/* 5.25 => 0 */ 0x00, /* => 10.02 */ +/* 5.50 => 0 */ 0x00, /* => 10.02 */ +/* 5.75 => 0 */ 0x00, /* => 10.02 */ +/* 6.00 => 0 */ 0x00, /* => 10.02 */ +/* 6.25 => 0 */ 0x00, /* => 10.02 */ +/* 6.50 => 0 */ 0x00, /* => 10.02 */ +/* 6.75 => 0 */ 0x00, /* => 10.02 */ +/* 7.00 => 0 */ 0x00, /* => 10.02 */ +/* 7.25 => 0 */ 0x00, /* => 10.02 */ +/* 7.50 => 0 */ 0x00, /* => 10.02 */ +/* 7.75 => 0 */ 0x00, /* => 10.02 */ +/* 8.00 => 0 */ 0x00, /* => 10.02 */ +/* 8.50 => 0 */ 0x00, /* => 10.02 */ +/* 9.00 => 0 */ 0x00, /* => 10.02 */ +/* 9.50 => 0 */ 0x00, /* => 10.02 */ +/* 10.00 => 0 */ 0x00, /* => 10.02 */ +/* 10.50 => 1 */ 0x01, /* => 10.42 */ +/* 11.00 => 2 */ 0x02, /* => 10.86 */ +/* 11.50 => 3 */ 0x03, /* => 11.31 */ +/* 12.00 => 4 */ 0x04, /* => 11.78 */ +/* 12.50 => 5 */ 0x05, /* => 12.28 */ +/* 13.00 => 6 */ 0x06, /* => 12.80 */ +/* 13.50 => 7 */ 0x07, /* => 13.33 */ +/* 14.00 => 8 */ 0x08, /* => 13.89 */ +/* 14.50 => 9 */ 0x09, /* => 14.48 */ +/* 15.00 => 9 */ 0x09, /* => 14.48 */ +/* 15.50 => 10 */ 0x0a, /* => 15.08 */ +/* 16.00 => 11 */ 0x0b, /* => 15.72 */ +/* 17.00 => 12 */ 0x0c, /* => 16.38 */ +/* 18.00 => 14 */ 0x0e, /* => 17.75 */ +/* 19.00 => 15 */ 0x0f, /* => 18.50 */ +/* 20.00 => 16 */ 0x10, /* => 19.28 */ +/* 21.00 => 18 */ 0x12, /* => 20.94 */ +/* 22.00 => 19 */ 0x13, /* => 21.81 */ +/* 23.00 => 20 */ 0x14, /* => 22.75 */ +/* 24.00 => 21 */ 0x15, /* => 23.69 */ +/* 25.00 => 22 */ 0x16, /* => 24.69 */ +/* 26.00 => 23 */ 0x17, /* => 25.72 */ +/* 27.00 => 24 */ 0x18, /* => 26.81 */ +/* 28.00 => 25 */ 0x19, /* => 27.94 */ +/* 29.00 => 25 */ 0x19, /* => 27.94 */ +/* 30.00 => 26 */ 0x1a, /* => 29.09 */ +/* 31.00 => 27 */ 0x1b, /* => 30.31 */ +/* 32.00 => 28 */ 0x1c, /* => 31.56 */ +/* 34.00 => 29 */ 0x1d, /* => 32.94 */ +/* 36.00 => 31 */ 0x1f, /* => 35.69 */ +/* 38.00 => 32 */ 0x20, /* => 37.19 */ +/* 40.00 => 33 */ 0x21, /* => 38.75 */ +/* 42.00 => 34 */ 0x22, /* => 40.38 */ +/* 44.00 => 36 */ 0x24, /* => 43.88 */ +/* 46.00 => 37 */ 0x25, /* => 45.69 */ +/* 48.00 => 38 */ 0x26, /* => 47.62 */ +/* 50.00 => 39 */ 0x27, /* => 49.62 */ +/* 52.00 => 40 */ 0x28, /* => 51.69 */ +/* 54.00 => 41 */ 0x29, /* => 53.88 */ +/* 56.00 => 41 */ 0x29, /* => 53.88 */ +/* 58.00 => 42 */ 0x2a, /* => 56.12 */ +/* 60.00 => 43 */ 0x2b, /* => 58.44 */ +/* 62.00 => 44 */ 0x2c, /* => 60.94 */ +/* 64.00 => 45 */ 0x2d, /* => 63.50 */ +/* 68.00 => 46 */ 0x2e, /* => 66.12 */ +/* 72.00 => 48 */ 0x30, /* => 71.88 */ +/* 76.00 => 49 */ 0x31, /* => 74.75 */ +/* 80.00 => 50 */ 0x32, /* => 78.00 */ +/* 84.00 => 51 */ 0x33, /* => 81.25 */ +/* 88.00 => 52 */ 0x34, /* => 84.62 */ +/* 92.00 => 54 */ 0x36, /* => 91.88 */ +/* 96.00 => 55 */ 0x37, /* => 95.75 */ +/* 100.00 => 56 */ 0x38, /* => 99.75 */ +/* 104.00 => 56 */ 0x38, /* => 99.75 */ +/* 108.00 => 57 */ 0x39, /* => 104.00 */ +/* 112.00 => 58 */ 0x3a, /* => 108.25 */ +/* 116.00 => 59 */ 0x3b, /* => 112.88 */ +/* 120.00 => 60 */ 0x3c, /* => 117.50 */ +/* 124.00 => 61 */ 0x3d, /* => 122.38 */ +/* 128.00 => 62 */ 0x3e, /* => 127.50 */ +/* 136.00 => 63 */ 0x3f, /* => 132.75 */ +/* 144.00 => 64 */ 0x40, /* => 138.50 */ +/* 152.00 => 66 */ 0x42, /* => 150.25 */ +/* 160.00 => 67 */ 0x43, /* => 156.75 */ +/* 168.00 => 68 */ 0x44, /* => 163.50 */ +/* 176.00 => 69 */ 0x45, /* => 170.00 */ +/* 184.00 => 70 */ 0x46, /* => 177.25 */ +/* 192.00 => 71 */ 0x47, /* => 184.50 */ +/* 200.00 => 72 */ 0x48, /* => 192.25 */ +/* 208.00 => 73 */ 0x49, /* => 200.25 */ +/* 216.00 => 74 */ 0x4a, /* => 208.75 */ +/* 224.00 => 75 */ 0x4b, /* => 217.75 */ +/* 232.00 => 76 */ 0x4c, /* => 226.75 */ +/* 240.00 => 77 */ 0x4d, /* => 236.25 */ +/* 248.00 => 78 */ 0x4e, /* => 246.25 */ +/* 256.00 => 78 */ 0x4e, /* => 246.25 */ +/* 272.00 => 80 */ 0x50, /* => 267.50 */ +/* 288.00 => 81 */ 0x51, /* => 278.50 */ +/* 304.00 => 83 */ 0x53, /* => 302.00 */ +/* 320.00 => 84 */ 0x54, /* => 315.00 */ +/* 336.00 => 85 */ 0x55, /* => 328.00 */ +/* 352.00 => 86 */ 0x56, /* => 342.00 */ +/* 368.00 => 87 */ 0x57, /* => 356.00 */ +/* 384.00 => 88 */ 0x58, /* => 371.00 */ +/* 400.00 => 89 */ 0x59, /* => 386.50 */ +/* 416.00 => 90 */ 0x5a, /* => 403.00 */ +/* 432.00 => 91 */ 0x5b, /* => 419.50 */ +/* 448.00 => 92 */ 0x5c, /* => 437.50 */ +/* 464.00 => 93 */ 0x5d, /* => 455.50 */ +/* 480.00 => 94 */ 0x5e, /* => 475.00 */ +/* 496.00 => 95 */ 0x5f, /* => 495.00 */ +/* 512.00 => 95 */ 0x5f, /* => 495.00 */ +/* 544.00 => 97 */ 0x61, /* => 537.00 */ +/* 576.00 => 98 */ 0x62, /* => 559.00 */ +/* 608.00 => 100 */ 0x64, /* => 607.00 */ +/* 640.00 => 101 */ 0x65, /* => 632.00 */ +/* 672.00 => 102 */ 0x66, /* => 660.00 */ +/* 704.00 => 103 */ 0x67, /* => 687.00 */ +/* 736.00 => 104 */ 0x68, /* => 716.00 */ +/* 768.00 => 105 */ 0x69, /* => 746.00 */ +/* 800.00 => 106 */ 0x6a, /* => 777.00 */ +/* 832.00 => 107 */ 0x6b, /* => 810.00 */ +/* 864.00 => 108 */ 0x6c, /* => 843.00 */ +/* 896.00 => 109 */ 0x6d, /* => 879.00 */ +/* 928.00 => 110 */ 0x6e, /* => 916.00 */ +/* 960.00 => 111 */ 0x6f, /* => 954.00 */ +/* 992.00 => 111 */ 0x6f, /* => 954.00 */ +/* 1024.00 => 112 */ 0x70, /* => 994.00 */ +/* 1088.00 => 114 */ 0x72, /* => 1080.00 */ +/* 1152.00 => 115 */ 0x73, /* => 1124.00 */ +/* 1216.00 => 116 */ 0x74, /* => 1172.00 */ +/* 1280.00 => 118 */ 0x76, /* => 1272.00 */ +/* 1344.00 => 119 */ 0x77, /* => 1326.00 */ +/* 1408.00 => 120 */ 0x78, /* => 1382.00 */ +/* 1472.00 => 121 */ 0x79, /* => 1440.00 */ +/* 1536.00 => 122 */ 0x7a, /* => 1498.00 */ +/* 1600.00 => 123 */ 0x7b, /* => 1562.00 */ +/* 1664.00 => 124 */ 0x7c, /* => 1628.00 */ +/* 1728.00 => 125 */ 0x7d, /* => 1696.00 */ +/* 1792.00 => 126 */ 0x7e, /* => 1768.00 */ +/* 1856.00 => 127 */ 0x7f, /* => 1842.00 */ +/* 1920.00 => 128 */ 0x80, /* => 1918.00 */ +/* 1984.00 => 128 */ 0x80, /* => 1918.00 */ +/* 2048.00 => 129 */ 0x81, /* => 2000.00 */ +/* 2176.00 => 131 */ 0x83, /* => 2168.00 */ +/* 2304.00 => 132 */ 0x84, /* => 2264.00 */ +/* 2432.00 => 133 */ 0x85, /* => 2356.00 */ +/* 2560.00 => 135 */ 0x87, /* => 2556.00 */ +/* 2688.00 => 136 */ 0x88, /* => 2664.00 */ +/* 2816.00 => 137 */ 0x89, /* => 2776.00 */ +/* 2944.00 => 138 */ 0x8a, /* => 2892.00 */ +/* 3072.00 => 139 */ 0x8b, /* => 3012.00 */ +/* 3200.00 => 140 */ 0x8c, /* => 3140.00 */ +/* 3328.00 => 141 */ 0x8d, /* => 3272.00 */ +/* 3456.00 => 142 */ 0x8e, /* => 3412.00 */ +/* 3584.00 => 143 */ 0x8f, /* => 3552.00 */ +/* 3712.00 => 144 */ 0x90, /* => 3700.00 */ +/* 3840.00 => 144 */ 0x90, /* => 3700.00 */ +/* 3968.00 => 145 */ 0x91, /* => 3860.00 */ +/* 4096.00 => 146 */ 0x92, /* => 4016.00 */ +/* 4352.00 => 147 */ 0x93, /* => 4184.00 */ +/* 4608.00 => 149 */ 0x95, /* => 4544.00 */ +/* 4864.00 => 150 */ 0x96, /* => 4736.00 */ +/* 5120.00 => 151 */ 0x97, /* => 4936.00 */ +/* 5376.00 => 153 */ 0x99, /* => 5360.00 */ +/* 5632.00 => 154 */ 0x9a, /* => 5584.00 */ +/* 5888.00 => 155 */ 0x9b, /* => 5816.00 */ +/* 6144.00 => 156 */ 0x9c, /* => 6056.00 */ +/* 6400.00 => 157 */ 0x9d, /* => 6312.00 */ +/* 6656.00 => 158 */ 0x9e, /* => 6576.00 */ +/* 6912.00 => 159 */ 0x9f, /* => 6856.00 */ +/* 7168.00 => 160 */ 0xa0, /* => 7144.00 */ +/* 7424.00 => 160 */ 0xa0, /* => 7144.00 */ +/* 7680.00 => 161 */ 0xa1, /* => 7440.00 */ +/* 7936.00 => 162 */ 0xa2, /* => 7752.00 */ +/* 8192.00 => 163 */ 0xa3, /* => 8080.00 */ +/* 8704.00 => 164 */ 0xa4, /* => 8416.00 */ +/* 9216.00 => 166 */ 0xa6, /* => 9136.00 */ +/* 9728.00 => 167 */ 0xa7, /* => 9520.00 */ +/* 10240.00 => 168 */ 0xa8, /* => 9920.00 */ +/* 10752.00 => 169 */ 0xa9, /* => 10336.00 */ +/* 11264.00 => 171 */ 0xab, /* => 11216.00 */ +/* 11776.00 => 172 */ 0xac, /* => 11680.00 */ +/* 12288.00 => 173 */ 0xad, /* => 12176.00 */ +/* 12800.00 => 174 */ 0xae, /* => 12688.00 */ +/* 13312.00 => 175 */ 0xaf, /* => 13216.00 */ +/* 13824.00 => 176 */ 0xb0, /* => 13776.00 */ +/* 14336.00 => 176 */ 0xb0, /* => 13776.00 */ +/* 14848.00 => 177 */ 0xb1, /* => 14352.00 */ +/* 15360.00 => 178 */ 0xb2, /* => 14960.00 */ +/* 15872.00 => 179 */ 0xb3, /* => 15584.00 */ +/* 16384.00 => 180 */ 0xb4, /* => 16224.00 */ +/* 17408.00 => 181 */ 0xb5, /* => 16896.00 */ +/* 18432.00 => 183 */ 0xb7, /* => 18368.00 */ +/* 19456.00 => 184 */ 0xb8, /* => 19136.00 */ +/* 20480.00 => 185 */ 0xb9, /* => 19904.00 */ +/* 21504.00 => 186 */ 0xba, /* => 20768.00 */ +/* 22528.00 => 187 */ 0xbb, /* => 21632.00 */ +/* 23552.00 => 189 */ 0xbd, /* => 23488.00 */ +/* 24576.00 => 190 */ 0xbe, /* => 24512.00 */ +/* 25600.00 => 191 */ 0xbf, /* => 25504.00 */ +/* 26624.00 => 192 */ 0xc0, /* => 26592.00 */ +/* 27648.00 => 192 */ 0xc0, /* => 26592.00 */ +/* 28672.00 => 193 */ 0xc1, /* => 27680.00 */ +/* 29696.00 => 194 */ 0xc2, /* => 28896.00 */ +/* 30720.00 => 195 */ 0xc3, /* => 30048.00 */ +/* 31744.00 => 196 */ 0xc4, /* => 31392.00 */ +/* 32768.00 => 197 */ 0xc5, /* => 32640.00 */ +/* 34816.00 => 198 */ 0xc6, /* => 33984.00 */ +/* 36864.00 => 199 */ 0xc7, /* => 35392.00 */ +/* 38912.00 => 201 */ 0xc9, /* => 38528.00 */ +/* 40960.00 => 202 */ 0xca, /* => 40064.00 */ +/* 43008.00 => 203 */ 0xcb, /* => 41856.00 */ +/* 45056.00 => 204 */ 0xcc, /* => 43584.00 */ +/* 47104.00 => 205 */ 0xcd, /* => 45376.00 */ +/* 49152.00 => 206 */ 0xce, /* => 47232.00 */ +/* 51200.00 => 207 */ 0xcf, /* => 49344.00 */ +/* 53248.00 => 208 */ 0xd0, /* => 51328.00 */ +/* 55296.00 => 209 */ 0xd1, /* => 53504.00 */ +/* 57344.00 => 210 */ 0xd2, /* => 55616.00 */ +/* 59392.00 => 211 */ 0xd3, /* => 58240.00 */ +/* 61440.00 => 212 */ 0xd4, /* => 60416.00 */ +/* 63488.00 => 213 */ 0xd5, /* => 63104.00 */ +/* 65536.00 => 213 */ 0xd5, /* => 63104.00 */ +/* 69632.00 => 215 */ 0xd7, /* => 68480.00 */ +/* 73728.00 => 216 */ 0xd8, /* => 71424.00 */ +/* 77824.00 => 218 */ 0xda, /* => 77312.00 */ +/* 81920.00 => 219 */ 0xdb, /* => 80640.00 */ +/* 86016.00 => 220 */ 0xdc, /* => 84224.00 */ +/* 90112.00 => 221 */ 0xdd, /* => 87552.00 */ +/* 94208.00 => 222 */ 0xde, /* => 91136.00 */ +/* 98304.00 => 223 */ 0xdf, /* => 94976.00 */ +/* 102400.00 => 224 */ 0xe0, /* => 99072.00 */ +/* 106496.00 => 225 */ 0xe1, /* => 103680.00 */ +/* 110592.00 => 226 */ 0xe2, /* => 107520.00 */ +/* 114688.00 => 227 */ 0xe3, /* => 111872.00 */ +/* 118784.00 => 228 */ 0xe4, /* => 117632.00 */ +/* 122880.00 => 229 */ 0xe5, /* => 121472.00 */ +/* 126976.00 => 229 */ 0xe5, /* => 121472.00 */ +/* 131072.00 => 230 */ 0xe6, /* => 126976.00 */ +/* 139264.00 => 232 */ 0xe8, /* => 137728.00 */ +/* 147456.00 => 233 */ 0xe9, /* => 144896.00 */ +/* 155648.00 => 234 */ 0xea, /* => 150528.00 */ +/* 163840.00 => 236 */ 0xec, /* => 163584.00 */ +/* 172032.00 => 237 */ 0xed, /* => 168448.00 */ +/* 180224.00 => 238 */ 0xee, /* => 176384.00 */ +/* 188416.00 => 239 */ 0xef, /* => 185088.00 */ +/* 196608.00 => 240 */ 0xf0, /* => 191488.00 */ +/* 204800.00 => 241 */ 0xf1, /* => 201728.00 */ +/* 212992.00 => 242 */ 0xf2, /* => 209152.00 */ +/* 221184.00 => 243 */ 0xf3, /* => 217344.00 */ +/* 229376.00 => 244 */ 0xf4, /* => 226048.00 */ +/* 237568.00 => 245 */ 0xf5, /* => 235264.00 */ +/* 245760.00 => 246 */ 0xf6, /* => 245504.00 */ +/* 253952.00 => 246 */ 0xf6, /* => 245504.00 */ +/* 262144.00 => 247 */ 0xf7, /* => 256768.00 */ +/* 278528.00 => 248 */ 0xf8, /* => 268800.00 */ +/* 294912.00 => 250 */ 0xfa, /* => 289792.00 */ +/* 311296.00 => 251 */ 0xfb, /* => 305152.00 */ +/* 327680.00 => 252 */ 0xfc, /* => 313856.00 */ +/* 344064.00 => 254 */ 0xfe, /* => 342016.00 */ +/* 360448.00 => 255 */ 0xff, /* => 352768.00 */ +/* 376832.00 => 255 */ 0xff, /* => 352768.00 */ +/* 393216.00 => 255 */ 0xff, /* => 352768.00 */ +/* 409600.00 => 255 */ 0xff, /* => 352768.00 */ +/* 425984.00 => 255 */ 0xff, /* => 352768.00 */ +/* 442368.00 => 255 */ 0xff, /* => 352768.00 */ +/* 458752.00 => 255 */ 0xff, /* => 352768.00 */ +/* 475136.00 => 255 */ 0xff, /* => 352768.00 */ +/* 491520.00 => 255 */ 0xff, /* => 352768.00 */ +/* 507904.00 => 255 */ 0xff, /* => 352768.00 */ +/* 524288.00 => 255 */ 0xff, /* => 352768.00 */ +/* 557056.00 => 255 */ 0xff, /* => 352768.00 */ +/* 589824.00 => 255 */ 0xff, /* => 352768.00 */ +/* 622592.00 => 255 */ 0xff, /* => 352768.00 */ +/* 655360.00 => 255 */ 0xff, /* => 352768.00 */ +/* 688128.00 => 255 */ 0xff, /* => 352768.00 */ +/* 720896.00 => 255 */ 0xff, /* => 352768.00 */ +/* 753664.00 => 255 */ 0xff, /* => 352768.00 */ +/* 786432.00 => 255 */ 0xff, /* => 352768.00 */ +/* 819200.00 => 255 */ 0xff, /* => 352768.00 */ +/* 851968.00 => 255 */ 0xff, /* => 352768.00 */ +/* 884736.00 => 255 */ 0xff, /* => 352768.00 */ +/* 917504.00 => 255 */ 0xff, /* => 352768.00 */ +/* 950272.00 => 255 */ 0xff, /* => 352768.00 */ +/* 983040.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1015808.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1048576.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1114112.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1179648.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1245184.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1310720.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1376256.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1441792.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1507328.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1572864.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1638400.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1703936.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1769472.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1835008.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1900544.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1966080.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2031616.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2097152.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2228224.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2359296.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2490368.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2621440.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2752512.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2883584.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3014656.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3145728.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3276800.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3407872.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3538944.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3670016.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3801088.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3932160.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4063232.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4194304.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4456448.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4718592.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4980736.00 => 255 */ 0xff, /* => 352768.00 */ +/* 5242880.00 => 255 */ 0xff, /* => 352768.00 */ +/* 5505024.00 => 255 */ 0xff, /* => 352768.00 */ +/* 5767168.00 => 255 */ 0xff, /* => 352768.00 */ +/* 6029312.00 => 255 */ 0xff, /* => 352768.00 */ +/* 6291456.00 => 255 */ 0xff, /* => 352768.00 */ +/* 6553600.00 => 255 */ 0xff, /* => 352768.00 */ +/* 6815744.00 => 255 */ 0xff, /* => 352768.00 */ +/* 7077888.00 => 255 */ 0xff, /* => 352768.00 */ +/* 7340032.00 => 255 */ 0xff, /* => 352768.00 */ +/* 7602176.00 => 255 */ 0xff, /* => 352768.00 */ +/* 7864320.00 => 255 */ 0xff, /* => 352768.00 */ +/* 8126464.00 => 255 */ 0xff, /* => 352768.00 */ +/* 8388608.00 => 255 */ 0xff, /* => 352768.00 */ +/* 8912896.00 => 255 */ 0xff, /* => 352768.00 */ +/* 9437184.00 => 255 */ 0xff, /* => 352768.00 */ +/* 9961472.00 => 255 */ 0xff, /* => 352768.00 */ +/* 10485760.00 => 255 */ 0xff, /* => 352768.00 */ +/* 11010048.00 => 255 */ 0xff, /* => 352768.00 */ +/* 11534336.00 => 255 */ 0xff, /* => 352768.00 */ +/* 12058624.00 => 255 */ 0xff, /* => 352768.00 */ +/* 12582912.00 => 255 */ 0xff, /* => 352768.00 */ +/* 13107200.00 => 255 */ 0xff, /* => 352768.00 */ +/* 13631488.00 => 255 */ 0xff, /* => 352768.00 */ +/* 14155776.00 => 255 */ 0xff, /* => 352768.00 */ +/* 14680064.00 => 255 */ 0xff, /* => 352768.00 */ +/* 15204352.00 => 255 */ 0xff, /* => 352768.00 */ +/* 15728640.00 => 255 */ 0xff, /* => 352768.00 */ +/* 16252928.00 => 255 */ 0xff, /* => 352768.00 */ +/* 16777216.00 => 255 */ 0xff, /* => 352768.00 */ +/* 17825792.00 => 255 */ 0xff, /* => 352768.00 */ +/* 18874368.00 => 255 */ 0xff, /* => 352768.00 */ +/* 19922944.00 => 255 */ 0xff, /* => 352768.00 */ +/* 20971520.00 => 255 */ 0xff, /* => 352768.00 */ +/* 22020096.00 => 255 */ 0xff, /* => 352768.00 */ +/* 23068672.00 => 255 */ 0xff, /* => 352768.00 */ +/* 24117248.00 => 255 */ 0xff, /* => 352768.00 */ +/* 25165824.00 => 255 */ 0xff, /* => 352768.00 */ +/* 26214400.00 => 255 */ 0xff, /* => 352768.00 */ +/* 27262976.00 => 255 */ 0xff, /* => 352768.00 */ +/* 28311552.00 => 255 */ 0xff, /* => 352768.00 */ +/* 29360128.00 => 255 */ 0xff, /* => 352768.00 */ +/* 30408704.00 => 255 */ 0xff, /* => 352768.00 */ +/* 31457280.00 => 255 */ 0xff, /* => 352768.00 */ +/* 32505856.00 => 255 */ 0xff, /* => 352768.00 */ +/* 33554432.00 => 255 */ 0xff, /* => 352768.00 */ +/* 35651584.00 => 255 */ 0xff, /* => 352768.00 */ +/* 37748736.00 => 255 */ 0xff, /* => 352768.00 */ +/* 39845888.00 => 255 */ 0xff, /* => 352768.00 */ +/* 41943040.00 => 255 */ 0xff, /* => 352768.00 */ +/* 44040192.00 => 255 */ 0xff, /* => 352768.00 */ +/* 46137344.00 => 255 */ 0xff, /* => 352768.00 */ +/* 48234496.00 => 255 */ 0xff, /* => 352768.00 */ +/* 50331648.00 => 255 */ 0xff, /* => 352768.00 */ +/* 52428800.00 => 255 */ 0xff, /* => 352768.00 */ +/* 54525952.00 => 255 */ 0xff, /* => 352768.00 */ +/* 56623104.00 => 255 */ 0xff, /* => 352768.00 */ +/* 58720256.00 => 255 */ 0xff, /* => 352768.00 */ +/* 60817408.00 => 255 */ 0xff, /* => 352768.00 */ +/* 62914560.00 => 255 */ 0xff, /* => 352768.00 */ +/* 65011712.00 => 255 */ 0xff, /* => 352768.00 */ +/* 67108864.00 => 255 */ 0xff, /* => 352768.00 */ +/* 71303168.00 => 255 */ 0xff, /* => 352768.00 */ +/* 75497472.00 => 255 */ 0xff, /* => 352768.00 */ +/* 79691776.00 => 255 */ 0xff, /* => 352768.00 */ +/* 83886080.00 => 255 */ 0xff, /* => 352768.00 */ +/* 88080384.00 => 255 */ 0xff, /* => 352768.00 */ +/* 92274688.00 => 255 */ 0xff, /* => 352768.00 */ +/* 96468992.00 => 255 */ 0xff, /* => 352768.00 */ +/* 100663296.00 => 255 */ 0xff, /* => 352768.00 */ +/* 104857600.00 => 255 */ 0xff, /* => 352768.00 */ +/* 109051904.00 => 255 */ 0xff, /* => 352768.00 */ +/* 113246208.00 => 255 */ 0xff, /* => 352768.00 */ +/* 117440512.00 => 255 */ 0xff, /* => 352768.00 */ +/* 121634816.00 => 255 */ 0xff, /* => 352768.00 */ +/* 125829120.00 => 255 */ 0xff, /* => 352768.00 */ +/* 130023424.00 => 255 */ 0xff, /* => 352768.00 */ +/* 134217728.00 => 255 */ 0xff, /* => 352768.00 */ +/* 142606336.00 => 255 */ 0xff, /* => 352768.00 */ +/* 150994944.00 => 255 */ 0xff, /* => 352768.00 */ +/* 159383552.00 => 255 */ 0xff, /* => 352768.00 */ +/* 167772160.00 => 255 */ 0xff, /* => 352768.00 */ +/* 176160768.00 => 255 */ 0xff, /* => 352768.00 */ +/* 184549376.00 => 255 */ 0xff, /* => 352768.00 */ +/* 192937984.00 => 255 */ 0xff, /* => 352768.00 */ +/* 201326592.00 => 255 */ 0xff, /* => 352768.00 */ +/* 209715200.00 => 255 */ 0xff, /* => 352768.00 */ +/* 218103808.00 => 255 */ 0xff, /* => 352768.00 */ +/* 226492416.00 => 255 */ 0xff, /* => 352768.00 */ +/* 234881024.00 => 255 */ 0xff, /* => 352768.00 */ +/* 243269632.00 => 255 */ 0xff, /* => 352768.00 */ +/* 251658240.00 => 255 */ 0xff, /* => 352768.00 */ +/* 260046848.00 => 255 */ 0xff, /* => 352768.00 */ +/* 268435456.00 => 255 */ 0xff, /* => 352768.00 */ +/* 285212672.00 => 255 */ 0xff, /* => 352768.00 */ +/* 301989888.00 => 255 */ 0xff, /* => 352768.00 */ +/* 318767104.00 => 255 */ 0xff, /* => 352768.00 */ +/* 335544320.00 => 255 */ 0xff, /* => 352768.00 */ +/* 352321536.00 => 255 */ 0xff, /* => 352768.00 */ +/* 369098752.00 => 255 */ 0xff, /* => 352768.00 */ +/* 385875968.00 => 255 */ 0xff, /* => 352768.00 */ +/* 402653184.00 => 255 */ 0xff, /* => 352768.00 */ +/* 419430400.00 => 255 */ 0xff, /* => 352768.00 */ +/* 436207616.00 => 255 */ 0xff, /* => 352768.00 */ +/* 452984832.00 => 255 */ 0xff, /* => 352768.00 */ +/* 469762048.00 => 255 */ 0xff, /* => 352768.00 */ +/* 486539264.00 => 255 */ 0xff, /* => 352768.00 */ +/* 503316480.00 => 255 */ 0xff, /* => 352768.00 */ +/* 520093696.00 => 255 */ 0xff, /* => 352768.00 */ +/* 536870912.00 => 255 */ 0xff, /* => 352768.00 */ +/* 570425344.00 => 255 */ 0xff, /* => 352768.00 */ +/* 603979776.00 => 255 */ 0xff, /* => 352768.00 */ +/* 637534208.00 => 255 */ 0xff, /* => 352768.00 */ +/* 671088640.00 => 255 */ 0xff, /* => 352768.00 */ +/* 704643072.00 => 255 */ 0xff, /* => 352768.00 */ +/* 738197504.00 => 255 */ 0xff, /* => 352768.00 */ +/* 771751936.00 => 255 */ 0xff, /* => 352768.00 */ +/* 805306368.00 => 255 */ 0xff, /* => 352768.00 */ +/* 838860800.00 => 255 */ 0xff, /* => 352768.00 */ +/* 872415232.00 => 255 */ 0xff, /* => 352768.00 */ +/* 905969664.00 => 255 */ 0xff, /* => 352768.00 */ +/* 939524096.00 => 255 */ 0xff, /* => 352768.00 */ +/* 973078528.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1006632960.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1040187392.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1073741824.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1140850688.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1207959552.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1275068416.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1342177280.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1409286144.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1476395008.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1543503872.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1610612736.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1677721600.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1744830464.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1811939328.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1879048192.00 => 255 */ 0xff, /* => 352768.00 */ +/* 1946157056.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2013265920.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2080374784.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2147483648.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2281701376.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2415919104.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2550136832.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2684354560.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2818572288.00 => 255 */ 0xff, /* => 352768.00 */ +/* 2952790016.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3087007744.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3221225472.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3355443200.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3489660928.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3623878656.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3758096384.00 => 255 */ 0xff, /* => 352768.00 */ +/* 3892314112.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4026531840.00 => 255 */ 0xff, /* => 352768.00 */ +/* 4160749568.00 => 255 */ 0xff, /* => 352768.00 */ +}; diff --git a/drivers/atm/iphase.c b/drivers/atm/iphase.c new file mode 100644 index 000000000..cc90f550a --- /dev/null +++ b/drivers/atm/iphase.c @@ -0,0 +1,3288 @@ +/****************************************************************************** + iphase.c: Device driver for Interphase ATM PCI adapter cards + Author: Peter Wang + Some fixes: Arnaldo Carvalho de Melo + Interphase Corporation + Version: 1.0 +******************************************************************************* + + This software may be used and distributed according to the terms + of the GNU General Public License (GPL), incorporated herein by reference. + Drivers based on this skeleton fall under the GPL and must retain + the authorship (implicit copyright) notice. + + 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. + + Modified from an incomplete driver for Interphase 5575 1KVC 1M card which + was originally written by Monalisa Agrawal at UNH. Now this driver + supports a variety of varients of Interphase ATM PCI (i)Chip adapter + card family (See www.iphase.com/products/ClassSheet.cfm?ClassID=ATM) + in terms of PHY type, the size of control memory and the size of + packet memory. The following are the change log and history: + + Bugfix the Mona's UBR driver. + Modify the basic memory allocation and dma logic. + Port the driver to the latest kernel from 2.0.46. + Complete the ABR logic of the driver, and added the ABR work- + around for the hardware anormalies. + Add the CBR support. + Add the flow control logic to the driver to allow rate-limit VC. + Add 4K VC support to the board with 512K control memory. + Add the support of all the variants of the Interphase ATM PCI + (i)Chip adapter cards including x575 (155M OC3 and UTP155), x525 + (25M UTP25) and x531 (DS3 and E3). + Add SMP support. + + Support and updates available at: ftp://ftp.iphase.com/pub/atm + +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iphase.h" +#include "suni.h" +#define swap_byte_order(x) (((x & 0xff) << 8) | ((x & 0xff00) >> 8)) + +#define PRIV(dev) ((struct suni_priv *) dev->phy_data) + +static unsigned char ia_phy_get(struct atm_dev *dev, unsigned long addr); +static void desc_dbg(IADEV *iadev); + +static IADEV *ia_dev[8]; +static struct atm_dev *_ia_dev[8]; +static int iadev_count; +static void ia_led_timer(struct timer_list *unused); +static DEFINE_TIMER(ia_timer, ia_led_timer); +static int IA_TX_BUF = DFL_TX_BUFFERS, IA_TX_BUF_SZ = DFL_TX_BUF_SZ; +static int IA_RX_BUF = DFL_RX_BUFFERS, IA_RX_BUF_SZ = DFL_RX_BUF_SZ; +static uint IADebugFlag = /* IF_IADBG_ERR | IF_IADBG_CBR| IF_IADBG_INIT_ADAPTER + |IF_IADBG_ABR | IF_IADBG_EVENT*/ 0; + +module_param(IA_TX_BUF, int, 0); +module_param(IA_TX_BUF_SZ, int, 0); +module_param(IA_RX_BUF, int, 0); +module_param(IA_RX_BUF_SZ, int, 0); +module_param(IADebugFlag, uint, 0644); + +MODULE_LICENSE("GPL"); + +/**************************** IA_LIB **********************************/ + +static void ia_init_rtn_q (IARTN_Q *que) +{ + que->next = NULL; + que->tail = NULL; +} + +static void ia_enque_head_rtn_q (IARTN_Q *que, IARTN_Q * data) +{ + data->next = NULL; + if (que->next == NULL) + que->next = que->tail = data; + else { + data->next = que->next; + que->next = data; + } + return; +} + +static int ia_enque_rtn_q (IARTN_Q *que, struct desc_tbl_t data) { + IARTN_Q *entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) + return -ENOMEM; + entry->data = data; + entry->next = NULL; + if (que->next == NULL) + que->next = que->tail = entry; + else { + que->tail->next = entry; + que->tail = que->tail->next; + } + return 1; +} + +static IARTN_Q * ia_deque_rtn_q (IARTN_Q *que) { + IARTN_Q *tmpdata; + if (que->next == NULL) + return NULL; + tmpdata = que->next; + if ( que->next == que->tail) + que->next = que->tail = NULL; + else + que->next = que->next->next; + return tmpdata; +} + +static void ia_hack_tcq(IADEV *dev) { + + u_short desc1; + u_short tcq_wr; + struct ia_vcc *iavcc_r = NULL; + + tcq_wr = readl(dev->seg_reg+TCQ_WR_PTR) & 0xffff; + while (dev->host_tcq_wr != tcq_wr) { + desc1 = *(u_short *)(dev->seg_ram + dev->host_tcq_wr); + if (!desc1) ; + else if (!dev->desc_tbl[desc1 -1].timestamp) { + IF_ABR(printk(" Desc %d is reset at %ld\n", desc1 -1, jiffies);) + *(u_short *) (dev->seg_ram + dev->host_tcq_wr) = 0; + } + else if (dev->desc_tbl[desc1 -1].timestamp) { + if (!(iavcc_r = dev->desc_tbl[desc1 -1].iavcc)) { + printk("IA: Fatal err in get_desc\n"); + continue; + } + iavcc_r->vc_desc_cnt--; + dev->desc_tbl[desc1 -1].timestamp = 0; + IF_EVENT(printk("ia_hack: return_q skb = 0x%p desc = %d\n", + dev->desc_tbl[desc1 -1].txskb, desc1);) + if (iavcc_r->pcr < dev->rate_limit) { + IA_SKB_STATE (dev->desc_tbl[desc1-1].txskb) |= IA_TX_DONE; + if (ia_enque_rtn_q(&dev->tx_return_q, dev->desc_tbl[desc1 -1]) < 0) + printk("ia_hack_tcq: No memory available\n"); + } + dev->desc_tbl[desc1 -1].iavcc = NULL; + dev->desc_tbl[desc1 -1].txskb = NULL; + } + dev->host_tcq_wr += 2; + if (dev->host_tcq_wr > dev->ffL.tcq_ed) + dev->host_tcq_wr = dev->ffL.tcq_st; + } +} /* ia_hack_tcq */ + +static u16 get_desc (IADEV *dev, struct ia_vcc *iavcc) { + u_short desc_num, i; + struct sk_buff *skb; + struct ia_vcc *iavcc_r = NULL; + unsigned long delta; + static unsigned long timer = 0; + int ltimeout; + + ia_hack_tcq (dev); + if((time_after(jiffies,timer+50)) || ((dev->ffL.tcq_rd==dev->host_tcq_wr))) { + timer = jiffies; + i=0; + while (i < dev->num_tx_desc) { + if (!dev->desc_tbl[i].timestamp) { + i++; + continue; + } + ltimeout = dev->desc_tbl[i].iavcc->ltimeout; + delta = jiffies - dev->desc_tbl[i].timestamp; + if (delta >= ltimeout) { + IF_ABR(printk("RECOVER run!! desc_tbl %d = %d delta = %ld, time = %ld\n", i,dev->desc_tbl[i].timestamp, delta, jiffies);) + if (dev->ffL.tcq_rd == dev->ffL.tcq_st) + dev->ffL.tcq_rd = dev->ffL.tcq_ed; + else + dev->ffL.tcq_rd -= 2; + *(u_short *)(dev->seg_ram + dev->ffL.tcq_rd) = i+1; + if (!(skb = dev->desc_tbl[i].txskb) || + !(iavcc_r = dev->desc_tbl[i].iavcc)) + printk("Fatal err, desc table vcc or skb is NULL\n"); + else + iavcc_r->vc_desc_cnt--; + dev->desc_tbl[i].timestamp = 0; + dev->desc_tbl[i].iavcc = NULL; + dev->desc_tbl[i].txskb = NULL; + } + i++; + } /* while */ + } + if (dev->ffL.tcq_rd == dev->host_tcq_wr) + return 0xFFFF; + + /* Get the next available descriptor number from TCQ */ + desc_num = *(u_short *)(dev->seg_ram + dev->ffL.tcq_rd); + + while (!desc_num || (dev->desc_tbl[desc_num -1]).timestamp) { + dev->ffL.tcq_rd += 2; + if (dev->ffL.tcq_rd > dev->ffL.tcq_ed) + dev->ffL.tcq_rd = dev->ffL.tcq_st; + if (dev->ffL.tcq_rd == dev->host_tcq_wr) + return 0xFFFF; + desc_num = *(u_short *)(dev->seg_ram + dev->ffL.tcq_rd); + } + + /* get system time */ + dev->desc_tbl[desc_num -1].timestamp = jiffies; + return desc_num; +} + +static void clear_lockup (struct atm_vcc *vcc, IADEV *dev) { + u_char foundLockUp; + vcstatus_t *vcstatus; + u_short *shd_tbl; + u_short tempCellSlot, tempFract; + struct main_vc *abr_vc = (struct main_vc *)dev->MAIN_VC_TABLE_ADDR; + struct ext_vc *eabr_vc = (struct ext_vc *)dev->EXT_VC_TABLE_ADDR; + u_int i; + + if (vcc->qos.txtp.traffic_class == ATM_ABR) { + vcstatus = (vcstatus_t *) &(dev->testTable[vcc->vci]->vc_status); + vcstatus->cnt++; + foundLockUp = 0; + if( vcstatus->cnt == 0x05 ) { + abr_vc += vcc->vci; + eabr_vc += vcc->vci; + if( eabr_vc->last_desc ) { + if( (abr_vc->status & 0x07) == ABR_STATE /* 0x2 */ ) { + /* Wait for 10 Micro sec */ + udelay(10); + if ((eabr_vc->last_desc)&&((abr_vc->status & 0x07)==ABR_STATE)) + foundLockUp = 1; + } + else { + tempCellSlot = abr_vc->last_cell_slot; + tempFract = abr_vc->fraction; + if((tempCellSlot == dev->testTable[vcc->vci]->lastTime) + && (tempFract == dev->testTable[vcc->vci]->fract)) + foundLockUp = 1; + dev->testTable[vcc->vci]->lastTime = tempCellSlot; + dev->testTable[vcc->vci]->fract = tempFract; + } + } /* last descriptor */ + vcstatus->cnt = 0; + } /* vcstatus->cnt */ + + if (foundLockUp) { + IF_ABR(printk("LOCK UP found\n");) + writew(0xFFFD, dev->seg_reg+MODE_REG_0); + /* Wait for 10 Micro sec */ + udelay(10); + abr_vc->status &= 0xFFF8; + abr_vc->status |= 0x0001; /* state is idle */ + shd_tbl = (u_short *)dev->ABR_SCHED_TABLE_ADDR; + for( i = 0; ((i < dev->num_vc) && (shd_tbl[i])); i++ ); + if (i < dev->num_vc) + shd_tbl[i] = vcc->vci; + else + IF_ERR(printk("ABR Seg. may not continue on VC %x\n",vcc->vci);) + writew(T_ONLINE, dev->seg_reg+MODE_REG_0); + writew(~(TRANSMIT_DONE|TCQ_NOT_EMPTY), dev->seg_reg+SEG_MASK_REG); + writew(TRANSMIT_DONE, dev->seg_reg+SEG_INTR_STATUS_REG); + vcstatus->cnt = 0; + } /* foundLockUp */ + + } /* if an ABR VC */ + + +} + +/* +** Conversion of 24-bit cellrate (cells/sec) to 16-bit floating point format. +** +** +----+----+------------------+-------------------------------+ +** | R | NZ | 5-bit exponent | 9-bit mantissa | +** +----+----+------------------+-------------------------------+ +** +** R = reserved (written as 0) +** NZ = 0 if 0 cells/sec; 1 otherwise +** +** if NZ = 1, rate = 1.mmmmmmmmm x 2^(eeeee) cells/sec +*/ +static u16 +cellrate_to_float(u32 cr) +{ + +#define NZ 0x4000 +#define M_BITS 9 /* Number of bits in mantissa */ +#define E_BITS 5 /* Number of bits in exponent */ +#define M_MASK 0x1ff +#define E_MASK 0x1f + u16 flot; + u32 tmp = cr & 0x00ffffff; + int i = 0; + if (cr == 0) + return 0; + while (tmp != 1) { + tmp >>= 1; + i++; + } + if (i == M_BITS) + flot = NZ | (i << M_BITS) | (cr & M_MASK); + else if (i < M_BITS) + flot = NZ | (i << M_BITS) | ((cr << (M_BITS - i)) & M_MASK); + else + flot = NZ | (i << M_BITS) | ((cr >> (i - M_BITS)) & M_MASK); + return flot; +} + +#if 0 +/* +** Conversion of 16-bit floating point format to 24-bit cellrate (cells/sec). +*/ +static u32 +float_to_cellrate(u16 rate) +{ + u32 exp, mantissa, cps; + if ((rate & NZ) == 0) + return 0; + exp = (rate >> M_BITS) & E_MASK; + mantissa = rate & M_MASK; + if (exp == 0) + return 1; + cps = (1 << M_BITS) | mantissa; + if (exp == M_BITS) + cps = cps; + else if (exp > M_BITS) + cps <<= (exp - M_BITS); + else + cps >>= (M_BITS - exp); + return cps; +} +#endif + +static void init_abr_vc (IADEV *dev, srv_cls_param_t *srv_p) { + srv_p->class_type = ATM_ABR; + srv_p->pcr = dev->LineRate; + srv_p->mcr = 0; + srv_p->icr = 0x055cb7; + srv_p->tbe = 0xffffff; + srv_p->frtt = 0x3a; + srv_p->rif = 0xf; + srv_p->rdf = 0xb; + srv_p->nrm = 0x4; + srv_p->trm = 0x7; + srv_p->cdf = 0x3; + srv_p->adtf = 50; +} + +static int +ia_open_abr_vc(IADEV *dev, srv_cls_param_t *srv_p, + struct atm_vcc *vcc, u8 flag) +{ + f_vc_abr_entry *f_abr_vc; + r_vc_abr_entry *r_abr_vc; + u32 icr; + u8 trm, nrm, crm; + u16 adtf, air, *ptr16; + f_abr_vc =(f_vc_abr_entry *)dev->MAIN_VC_TABLE_ADDR; + f_abr_vc += vcc->vci; + switch (flag) { + case 1: /* FFRED initialization */ +#if 0 /* sanity check */ + if (srv_p->pcr == 0) + return INVALID_PCR; + if (srv_p->pcr > dev->LineRate) + srv_p->pcr = dev->LineRate; + if ((srv_p->mcr + dev->sum_mcr) > dev->LineRate) + return MCR_UNAVAILABLE; + if (srv_p->mcr > srv_p->pcr) + return INVALID_MCR; + if (!(srv_p->icr)) + srv_p->icr = srv_p->pcr; + if ((srv_p->icr < srv_p->mcr) || (srv_p->icr > srv_p->pcr)) + return INVALID_ICR; + if ((srv_p->tbe < MIN_TBE) || (srv_p->tbe > MAX_TBE)) + return INVALID_TBE; + if ((srv_p->frtt < MIN_FRTT) || (srv_p->frtt > MAX_FRTT)) + return INVALID_FRTT; + if (srv_p->nrm > MAX_NRM) + return INVALID_NRM; + if (srv_p->trm > MAX_TRM) + return INVALID_TRM; + if (srv_p->adtf > MAX_ADTF) + return INVALID_ADTF; + else if (srv_p->adtf == 0) + srv_p->adtf = 1; + if (srv_p->cdf > MAX_CDF) + return INVALID_CDF; + if (srv_p->rif > MAX_RIF) + return INVALID_RIF; + if (srv_p->rdf > MAX_RDF) + return INVALID_RDF; +#endif + memset ((caddr_t)f_abr_vc, 0, sizeof(*f_abr_vc)); + f_abr_vc->f_vc_type = ABR; + nrm = 2 << srv_p->nrm; /* (2 ** (srv_p->nrm +1)) */ + /* i.e 2**n = 2 << (n-1) */ + f_abr_vc->f_nrm = nrm << 8 | nrm; + trm = 100000/(2 << (16 - srv_p->trm)); + if ( trm == 0) trm = 1; + f_abr_vc->f_nrmexp =(((srv_p->nrm +1) & 0x0f) << 12)|(MRM << 8) | trm; + crm = srv_p->tbe / nrm; + if (crm == 0) crm = 1; + f_abr_vc->f_crm = crm & 0xff; + f_abr_vc->f_pcr = cellrate_to_float(srv_p->pcr); + icr = min( srv_p->icr, (srv_p->tbe > srv_p->frtt) ? + ((srv_p->tbe/srv_p->frtt)*1000000) : + (1000000/(srv_p->frtt/srv_p->tbe))); + f_abr_vc->f_icr = cellrate_to_float(icr); + adtf = (10000 * srv_p->adtf)/8192; + if (adtf == 0) adtf = 1; + f_abr_vc->f_cdf = ((7 - srv_p->cdf) << 12 | adtf) & 0xfff; + f_abr_vc->f_mcr = cellrate_to_float(srv_p->mcr); + f_abr_vc->f_acr = f_abr_vc->f_icr; + f_abr_vc->f_status = 0x0042; + break; + case 0: /* RFRED initialization */ + ptr16 = (u_short *)(dev->reass_ram + REASS_TABLE*dev->memSize); + *(ptr16 + vcc->vci) = NO_AAL5_PKT | REASS_ABR; + r_abr_vc = (r_vc_abr_entry*)(dev->reass_ram+ABR_VC_TABLE*dev->memSize); + r_abr_vc += vcc->vci; + r_abr_vc->r_status_rdf = (15 - srv_p->rdf) & 0x000f; + air = srv_p->pcr << (15 - srv_p->rif); + if (air == 0) air = 1; + r_abr_vc->r_air = cellrate_to_float(air); + dev->testTable[vcc->vci]->vc_status = VC_ACTIVE | VC_ABR; + dev->sum_mcr += srv_p->mcr; + dev->n_abr++; + break; + default: + break; + } + return 0; +} +static int ia_cbr_setup (IADEV *dev, struct atm_vcc *vcc) { + u32 rateLow=0, rateHigh, rate; + int entries; + struct ia_vcc *ia_vcc; + + int idealSlot =0, testSlot, toBeAssigned, inc; + u32 spacing; + u16 *SchedTbl, *TstSchedTbl; + u16 cbrVC, vcIndex; + u32 fracSlot = 0; + u32 sp_mod = 0; + u32 sp_mod2 = 0; + + /* IpAdjustTrafficParams */ + if (vcc->qos.txtp.max_pcr <= 0) { + IF_ERR(printk("PCR for CBR not defined\n");) + return -1; + } + rate = vcc->qos.txtp.max_pcr; + entries = rate / dev->Granularity; + IF_CBR(printk("CBR: CBR entries=0x%x for rate=0x%x & Gran=0x%x\n", + entries, rate, dev->Granularity);) + if (entries < 1) + IF_CBR(printk("CBR: Bandwidth smaller than granularity of CBR table\n");) + rateLow = entries * dev->Granularity; + rateHigh = (entries + 1) * dev->Granularity; + if (3*(rate - rateLow) > (rateHigh - rate)) + entries++; + if (entries > dev->CbrRemEntries) { + IF_CBR(printk("CBR: Not enough bandwidth to support this PCR.\n");) + IF_CBR(printk("Entries = 0x%x, CbrRemEntries = 0x%x.\n", + entries, dev->CbrRemEntries);) + return -EBUSY; + } + + ia_vcc = INPH_IA_VCC(vcc); + ia_vcc->NumCbrEntry = entries; + dev->sum_mcr += entries * dev->Granularity; + /* IaFFrednInsertCbrSched */ + // Starting at an arbitrary location, place the entries into the table + // as smoothly as possible + cbrVC = 0; + spacing = dev->CbrTotEntries / entries; + sp_mod = dev->CbrTotEntries % entries; // get modulo + toBeAssigned = entries; + fracSlot = 0; + vcIndex = vcc->vci; + IF_CBR(printk("Vci=0x%x,Spacing=0x%x,Sp_mod=0x%x\n",vcIndex,spacing,sp_mod);) + while (toBeAssigned) + { + // If this is the first time, start the table loading for this connection + // as close to entryPoint as possible. + if (toBeAssigned == entries) + { + idealSlot = dev->CbrEntryPt; + dev->CbrEntryPt += 2; // Adding 2 helps to prevent clumping + if (dev->CbrEntryPt >= dev->CbrTotEntries) + dev->CbrEntryPt -= dev->CbrTotEntries;// Wrap if necessary + } else { + idealSlot += (u32)(spacing + fracSlot); // Point to the next location + // in the table that would be smoothest + fracSlot = ((sp_mod + sp_mod2) / entries); // get new integer part + sp_mod2 = ((sp_mod + sp_mod2) % entries); // calc new fractional part + } + if (idealSlot >= (int)dev->CbrTotEntries) + idealSlot -= dev->CbrTotEntries; + // Continuously check around this ideal value until a null + // location is encountered. + SchedTbl = (u16*)(dev->seg_ram+CBR_SCHED_TABLE*dev->memSize); + inc = 0; + testSlot = idealSlot; + TstSchedTbl = (u16*)(SchedTbl+testSlot); //set index and read in value + IF_CBR(printk("CBR Testslot 0x%x AT Location 0x%p, NumToAssign=%d\n", + testSlot, TstSchedTbl,toBeAssigned);) + memcpy((caddr_t)&cbrVC,(caddr_t)TstSchedTbl,sizeof(cbrVC)); + while (cbrVC) // If another VC at this location, we have to keep looking + { + inc++; + testSlot = idealSlot - inc; + if (testSlot < 0) { // Wrap if necessary + testSlot += dev->CbrTotEntries; + IF_CBR(printk("Testslot Wrap. STable Start=0x%p,Testslot=%d\n", + SchedTbl,testSlot);) + } + TstSchedTbl = (u16 *)(SchedTbl + testSlot); // set table index + memcpy((caddr_t)&cbrVC,(caddr_t)TstSchedTbl,sizeof(cbrVC)); + if (!cbrVC) + break; + testSlot = idealSlot + inc; + if (testSlot >= (int)dev->CbrTotEntries) { // Wrap if necessary + testSlot -= dev->CbrTotEntries; + IF_CBR(printk("TotCbrEntries=%d",dev->CbrTotEntries);) + IF_CBR(printk(" Testslot=0x%x ToBeAssgned=%d\n", + testSlot, toBeAssigned);) + } + // set table index and read in value + TstSchedTbl = (u16*)(SchedTbl + testSlot); + IF_CBR(printk("Reading CBR Tbl from 0x%p, CbrVal=0x%x Iteration %d\n", + TstSchedTbl,cbrVC,inc);) + memcpy((caddr_t)&cbrVC,(caddr_t)TstSchedTbl,sizeof(cbrVC)); + } /* while */ + // Move this VCI number into this location of the CBR Sched table. + memcpy((caddr_t)TstSchedTbl, (caddr_t)&vcIndex, sizeof(*TstSchedTbl)); + dev->CbrRemEntries--; + toBeAssigned--; + } /* while */ + + /* IaFFrednCbrEnable */ + dev->NumEnabledCBR++; + if (dev->NumEnabledCBR == 1) { + writew((CBR_EN | UBR_EN | ABR_EN | (0x23 << 2)), dev->seg_reg+STPARMS); + IF_CBR(printk("CBR is enabled\n");) + } + return 0; +} +static void ia_cbrVc_close (struct atm_vcc *vcc) { + IADEV *iadev; + u16 *SchedTbl, NullVci = 0; + u32 i, NumFound; + + iadev = INPH_IA_DEV(vcc->dev); + iadev->NumEnabledCBR--; + SchedTbl = (u16*)(iadev->seg_ram+CBR_SCHED_TABLE*iadev->memSize); + if (iadev->NumEnabledCBR == 0) { + writew((UBR_EN | ABR_EN | (0x23 << 2)), iadev->seg_reg+STPARMS); + IF_CBR (printk("CBR support disabled\n");) + } + NumFound = 0; + for (i=0; i < iadev->CbrTotEntries; i++) + { + if (*SchedTbl == vcc->vci) { + iadev->CbrRemEntries++; + *SchedTbl = NullVci; + IF_CBR(NumFound++;) + } + SchedTbl++; + } + IF_CBR(printk("Exit ia_cbrVc_close, NumRemoved=%d\n",NumFound);) +} + +static int ia_avail_descs(IADEV *iadev) { + int tmp = 0; + ia_hack_tcq(iadev); + if (iadev->host_tcq_wr >= iadev->ffL.tcq_rd) + tmp = (iadev->host_tcq_wr - iadev->ffL.tcq_rd) / 2; + else + tmp = (iadev->ffL.tcq_ed - iadev->ffL.tcq_rd + 2 + iadev->host_tcq_wr - + iadev->ffL.tcq_st) / 2; + return tmp; +} + +static int ia_pkt_tx (struct atm_vcc *vcc, struct sk_buff *skb); + +static int ia_que_tx (IADEV *iadev) { + struct sk_buff *skb; + int num_desc; + struct atm_vcc *vcc; + num_desc = ia_avail_descs(iadev); + + while (num_desc && (skb = skb_dequeue(&iadev->tx_backlog))) { + if (!(vcc = ATM_SKB(skb)->vcc)) { + dev_kfree_skb_any(skb); + printk("ia_que_tx: Null vcc\n"); + break; + } + if (!test_bit(ATM_VF_READY,&vcc->flags)) { + dev_kfree_skb_any(skb); + printk("Free the SKB on closed vci %d \n", vcc->vci); + break; + } + if (ia_pkt_tx (vcc, skb)) { + skb_queue_head(&iadev->tx_backlog, skb); + } + num_desc--; + } + return 0; +} + +static void ia_tx_poll (IADEV *iadev) { + struct atm_vcc *vcc = NULL; + struct sk_buff *skb = NULL, *skb1 = NULL; + struct ia_vcc *iavcc; + IARTN_Q * rtne; + + ia_hack_tcq(iadev); + while ( (rtne = ia_deque_rtn_q(&iadev->tx_return_q))) { + skb = rtne->data.txskb; + if (!skb) { + printk("ia_tx_poll: skb is null\n"); + goto out; + } + vcc = ATM_SKB(skb)->vcc; + if (!vcc) { + printk("ia_tx_poll: vcc is null\n"); + dev_kfree_skb_any(skb); + goto out; + } + + iavcc = INPH_IA_VCC(vcc); + if (!iavcc) { + printk("ia_tx_poll: iavcc is null\n"); + dev_kfree_skb_any(skb); + goto out; + } + + skb1 = skb_dequeue(&iavcc->txing_skb); + while (skb1 && (skb1 != skb)) { + if (!(IA_SKB_STATE(skb1) & IA_TX_DONE)) { + printk("IA_tx_intr: Vci %d lost pkt!!!\n", vcc->vci); + } + IF_ERR(printk("Release the SKB not match\n");) + if ((vcc->pop) && (skb1->len != 0)) + { + vcc->pop(vcc, skb1); + IF_EVENT(printk("Transmit Done - skb 0x%lx return\n", + (long)skb1);) + } + else + dev_kfree_skb_any(skb1); + skb1 = skb_dequeue(&iavcc->txing_skb); + } + if (!skb1) { + IF_EVENT(printk("IA: Vci %d - skb not found requed\n",vcc->vci);) + ia_enque_head_rtn_q (&iadev->tx_return_q, rtne); + break; + } + if ((vcc->pop) && (skb->len != 0)) + { + vcc->pop(vcc, skb); + IF_EVENT(printk("Tx Done - skb 0x%lx return\n",(long)skb);) + } + else + dev_kfree_skb_any(skb); + kfree(rtne); + } + ia_que_tx(iadev); +out: + return; +} +#if 0 +static void ia_eeprom_put (IADEV *iadev, u32 addr, u_short val) +{ + u32 t; + int i; + /* + * Issue a command to enable writes to the NOVRAM + */ + NVRAM_CMD (EXTEND + EWEN); + NVRAM_CLR_CE; + /* + * issue the write command + */ + NVRAM_CMD(IAWRITE + addr); + /* + * Send the data, starting with D15, then D14, and so on for 16 bits + */ + for (i=15; i>=0; i--) { + NVRAM_CLKOUT (val & 0x8000); + val <<= 1; + } + NVRAM_CLR_CE; + CFG_OR(NVCE); + t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); + while (!(t & NVDO)) + t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); + + NVRAM_CLR_CE; + /* + * disable writes again + */ + NVRAM_CMD(EXTEND + EWDS) + NVRAM_CLR_CE; + CFG_AND(~NVDI); +} +#endif + +static u16 ia_eeprom_get (IADEV *iadev, u32 addr) +{ + u_short val; + u32 t; + int i; + /* + * Read the first bit that was clocked with the falling edge of the + * the last command data clock + */ + NVRAM_CMD(IAREAD + addr); + /* + * Now read the rest of the bits, the next bit read is D14, then D13, + * and so on. + */ + val = 0; + for (i=15; i>=0; i--) { + NVRAM_CLKIN(t); + val |= (t << i); + } + NVRAM_CLR_CE; + CFG_AND(~NVDI); + return val; +} + +static void ia_hw_type(IADEV *iadev) { + u_short memType = ia_eeprom_get(iadev, 25); + iadev->memType = memType; + if ((memType & MEM_SIZE_MASK) == MEM_SIZE_1M) { + iadev->num_tx_desc = IA_TX_BUF; + iadev->tx_buf_sz = IA_TX_BUF_SZ; + iadev->num_rx_desc = IA_RX_BUF; + iadev->rx_buf_sz = IA_RX_BUF_SZ; + } else if ((memType & MEM_SIZE_MASK) == MEM_SIZE_512K) { + if (IA_TX_BUF == DFL_TX_BUFFERS) + iadev->num_tx_desc = IA_TX_BUF / 2; + else + iadev->num_tx_desc = IA_TX_BUF; + iadev->tx_buf_sz = IA_TX_BUF_SZ; + if (IA_RX_BUF == DFL_RX_BUFFERS) + iadev->num_rx_desc = IA_RX_BUF / 2; + else + iadev->num_rx_desc = IA_RX_BUF; + iadev->rx_buf_sz = IA_RX_BUF_SZ; + } + else { + if (IA_TX_BUF == DFL_TX_BUFFERS) + iadev->num_tx_desc = IA_TX_BUF / 8; + else + iadev->num_tx_desc = IA_TX_BUF; + iadev->tx_buf_sz = IA_TX_BUF_SZ; + if (IA_RX_BUF == DFL_RX_BUFFERS) + iadev->num_rx_desc = IA_RX_BUF / 8; + else + iadev->num_rx_desc = IA_RX_BUF; + iadev->rx_buf_sz = IA_RX_BUF_SZ; + } + iadev->rx_pkt_ram = TX_PACKET_RAM + (iadev->num_tx_desc * iadev->tx_buf_sz); + IF_INIT(printk("BUF: tx=%d,sz=%d rx=%d sz= %d rx_pkt_ram=%d\n", + iadev->num_tx_desc, iadev->tx_buf_sz, iadev->num_rx_desc, + iadev->rx_buf_sz, iadev->rx_pkt_ram);) + +#if 0 + if ((memType & FE_MASK) == FE_SINGLE_MODE) { + iadev->phy_type = PHY_OC3C_S; + else if ((memType & FE_MASK) == FE_UTP_OPTION) + iadev->phy_type = PHY_UTP155; + else + iadev->phy_type = PHY_OC3C_M; +#endif + + iadev->phy_type = memType & FE_MASK; + IF_INIT(printk("memType = 0x%x iadev->phy_type = 0x%x\n", + memType,iadev->phy_type);) + if (iadev->phy_type == FE_25MBIT_PHY) + iadev->LineRate = (u32)(((25600000/8)*26)/(27*53)); + else if (iadev->phy_type == FE_DS3_PHY) + iadev->LineRate = (u32)(((44736000/8)*26)/(27*53)); + else if (iadev->phy_type == FE_E3_PHY) + iadev->LineRate = (u32)(((34368000/8)*26)/(27*53)); + else + iadev->LineRate = (u32)(ATM_OC3_PCR); + IF_INIT(printk("iadev->LineRate = %d \n", iadev->LineRate);) + +} + +static u32 ia_phy_read32(struct iadev_priv *ia, unsigned int reg) +{ + return readl(ia->phy + (reg >> 2)); +} + +static void ia_phy_write32(struct iadev_priv *ia, unsigned int reg, u32 val) +{ + writel(val, ia->phy + (reg >> 2)); +} + +static void ia_frontend_intr(struct iadev_priv *iadev) +{ + u32 status; + + if (iadev->phy_type & FE_25MBIT_PHY) { + status = ia_phy_read32(iadev, MB25_INTR_STATUS); + iadev->carrier_detect = (status & MB25_IS_GSB) ? 1 : 0; + } else if (iadev->phy_type & FE_DS3_PHY) { + ia_phy_read32(iadev, SUNI_DS3_FRM_INTR_STAT); + status = ia_phy_read32(iadev, SUNI_DS3_FRM_STAT); + iadev->carrier_detect = (status & SUNI_DS3_LOSV) ? 0 : 1; + } else if (iadev->phy_type & FE_E3_PHY) { + ia_phy_read32(iadev, SUNI_E3_FRM_MAINT_INTR_IND); + status = ia_phy_read32(iadev, SUNI_E3_FRM_FRAM_INTR_IND_STAT); + iadev->carrier_detect = (status & SUNI_E3_LOS) ? 0 : 1; + } else { + status = ia_phy_read32(iadev, SUNI_RSOP_STATUS); + iadev->carrier_detect = (status & SUNI_LOSV) ? 0 : 1; + } + + printk(KERN_INFO "IA: SUNI carrier %s\n", + iadev->carrier_detect ? "detected" : "lost signal"); +} + +static void ia_mb25_init(struct iadev_priv *iadev) +{ +#if 0 + mb25->mb25_master_ctrl = MB25_MC_DRIC | MB25_MC_DREC | MB25_MC_ENABLED; +#endif + ia_phy_write32(iadev, MB25_MASTER_CTRL, MB25_MC_DRIC | MB25_MC_DREC); + ia_phy_write32(iadev, MB25_DIAG_CONTROL, 0); + + iadev->carrier_detect = + (ia_phy_read32(iadev, MB25_INTR_STATUS) & MB25_IS_GSB) ? 1 : 0; +} + +struct ia_reg { + u16 reg; + u16 val; +}; + +static void ia_phy_write(struct iadev_priv *iadev, + const struct ia_reg *regs, int len) +{ + while (len--) { + ia_phy_write32(iadev, regs->reg, regs->val); + regs++; + } +} + +static void ia_suni_pm7345_init_ds3(struct iadev_priv *iadev) +{ + static const struct ia_reg suni_ds3_init[] = { + { SUNI_DS3_FRM_INTR_ENBL, 0x17 }, + { SUNI_DS3_FRM_CFG, 0x01 }, + { SUNI_DS3_TRAN_CFG, 0x01 }, + { SUNI_CONFIG, 0 }, + { SUNI_SPLR_CFG, 0 }, + { SUNI_SPLT_CFG, 0 } + }; + u32 status; + + status = ia_phy_read32(iadev, SUNI_DS3_FRM_STAT); + iadev->carrier_detect = (status & SUNI_DS3_LOSV) ? 0 : 1; + + ia_phy_write(iadev, suni_ds3_init, ARRAY_SIZE(suni_ds3_init)); +} + +static void ia_suni_pm7345_init_e3(struct iadev_priv *iadev) +{ + static const struct ia_reg suni_e3_init[] = { + { SUNI_E3_FRM_FRAM_OPTIONS, 0x04 }, + { SUNI_E3_FRM_MAINT_OPTIONS, 0x20 }, + { SUNI_E3_FRM_FRAM_INTR_ENBL, 0x1d }, + { SUNI_E3_FRM_MAINT_INTR_ENBL, 0x30 }, + { SUNI_E3_TRAN_STAT_DIAG_OPTIONS, 0 }, + { SUNI_E3_TRAN_FRAM_OPTIONS, 0x01 }, + { SUNI_CONFIG, SUNI_PM7345_E3ENBL }, + { SUNI_SPLR_CFG, 0x41 }, + { SUNI_SPLT_CFG, 0x41 } + }; + u32 status; + + status = ia_phy_read32(iadev, SUNI_E3_FRM_FRAM_INTR_IND_STAT); + iadev->carrier_detect = (status & SUNI_E3_LOS) ? 0 : 1; + ia_phy_write(iadev, suni_e3_init, ARRAY_SIZE(suni_e3_init)); +} + +static void ia_suni_pm7345_init(struct iadev_priv *iadev) +{ + static const struct ia_reg suni_init[] = { + /* Enable RSOP loss of signal interrupt. */ + { SUNI_INTR_ENBL, 0x28 }, + /* Clear error counters. */ + { SUNI_ID_RESET, 0 }, + /* Clear "PMCTST" in master test register. */ + { SUNI_MASTER_TEST, 0 }, + + { SUNI_RXCP_CTRL, 0x2c }, + { SUNI_RXCP_FCTRL, 0x81 }, + + { SUNI_RXCP_IDLE_PAT_H1, 0 }, + { SUNI_RXCP_IDLE_PAT_H2, 0 }, + { SUNI_RXCP_IDLE_PAT_H3, 0 }, + { SUNI_RXCP_IDLE_PAT_H4, 0x01 }, + + { SUNI_RXCP_IDLE_MASK_H1, 0xff }, + { SUNI_RXCP_IDLE_MASK_H2, 0xff }, + { SUNI_RXCP_IDLE_MASK_H3, 0xff }, + { SUNI_RXCP_IDLE_MASK_H4, 0xfe }, + + { SUNI_RXCP_CELL_PAT_H1, 0 }, + { SUNI_RXCP_CELL_PAT_H2, 0 }, + { SUNI_RXCP_CELL_PAT_H3, 0 }, + { SUNI_RXCP_CELL_PAT_H4, 0x01 }, + + { SUNI_RXCP_CELL_MASK_H1, 0xff }, + { SUNI_RXCP_CELL_MASK_H2, 0xff }, + { SUNI_RXCP_CELL_MASK_H3, 0xff }, + { SUNI_RXCP_CELL_MASK_H4, 0xff }, + + { SUNI_TXCP_CTRL, 0xa4 }, + { SUNI_TXCP_INTR_EN_STS, 0x10 }, + { SUNI_TXCP_IDLE_PAT_H5, 0x55 } + }; + + if (iadev->phy_type & FE_DS3_PHY) + ia_suni_pm7345_init_ds3(iadev); + else + ia_suni_pm7345_init_e3(iadev); + + ia_phy_write(iadev, suni_init, ARRAY_SIZE(suni_init)); + + ia_phy_write32(iadev, SUNI_CONFIG, ia_phy_read32(iadev, SUNI_CONFIG) & + ~(SUNI_PM7345_LLB | SUNI_PM7345_CLB | + SUNI_PM7345_DLB | SUNI_PM7345_PLB)); +#ifdef __SNMP__ + suni_pm7345->suni_rxcp_intr_en_sts |= SUNI_OOCDE; +#endif /* __SNMP__ */ + return; +} + + +/***************************** IA_LIB END *****************************/ + +#ifdef CONFIG_ATM_IA_DEBUG +static int tcnter = 0; +static void xdump( u_char* cp, int length, char* prefix ) +{ + int col, count; + u_char prntBuf[120]; + u_char* pBuf = prntBuf; + count = 0; + while(count < length){ + pBuf += sprintf( pBuf, "%s", prefix ); + for(col = 0;count + col < length && col < 16; col++){ + if (col != 0 && (col % 4) == 0) + pBuf += sprintf( pBuf, " " ); + pBuf += sprintf( pBuf, "%02X ", cp[count + col] ); + } + while(col++ < 16){ /* pad end of buffer with blanks */ + if ((col % 4) == 0) + sprintf( pBuf, " " ); + pBuf += sprintf( pBuf, " " ); + } + pBuf += sprintf( pBuf, " " ); + for(col = 0;count + col < length && col < 16; col++){ + if (isprint((int)cp[count + col])) + pBuf += sprintf( pBuf, "%c", cp[count + col] ); + else + pBuf += sprintf( pBuf, "." ); + } + printk("%s\n", prntBuf); + count += col; + pBuf = prntBuf; + } + +} /* close xdump(... */ +#endif /* CONFIG_ATM_IA_DEBUG */ + + +static struct atm_dev *ia_boards = NULL; + +#define ACTUAL_RAM_BASE \ + RAM_BASE*((iadev->mem)/(128 * 1024)) +#define ACTUAL_SEG_RAM_BASE \ + IPHASE5575_FRAG_CONTROL_RAM_BASE*((iadev->mem)/(128 * 1024)) +#define ACTUAL_REASS_RAM_BASE \ + IPHASE5575_REASS_CONTROL_RAM_BASE*((iadev->mem)/(128 * 1024)) + + +/*-- some utilities and memory allocation stuff will come here -------------*/ + +static void desc_dbg(IADEV *iadev) { + + u_short tcq_wr_ptr, tcq_st_ptr, tcq_ed_ptr; + u32 i; + void __iomem *tmp; + // regval = readl((u32)ia_cmds->maddr); + tcq_wr_ptr = readw(iadev->seg_reg+TCQ_WR_PTR); + printk("B_tcq_wr = 0x%x desc = %d last desc = %d\n", + tcq_wr_ptr, readw(iadev->seg_ram+tcq_wr_ptr), + readw(iadev->seg_ram+tcq_wr_ptr-2)); + printk(" host_tcq_wr = 0x%x host_tcq_rd = 0x%x \n", iadev->host_tcq_wr, + iadev->ffL.tcq_rd); + tcq_st_ptr = readw(iadev->seg_reg+TCQ_ST_ADR); + tcq_ed_ptr = readw(iadev->seg_reg+TCQ_ED_ADR); + printk("tcq_st_ptr = 0x%x tcq_ed_ptr = 0x%x \n", tcq_st_ptr, tcq_ed_ptr); + i = 0; + while (tcq_st_ptr != tcq_ed_ptr) { + tmp = iadev->seg_ram+tcq_st_ptr; + printk("TCQ slot %d desc = %d Addr = %p\n", i++, readw(tmp), tmp); + tcq_st_ptr += 2; + } + for(i=0; i num_tx_desc; i++) + printk("Desc_tbl[%d] = %d \n", i, iadev->desc_tbl[i].timestamp); +} + + +/*----------------------------- Receiving side stuff --------------------------*/ + +static void rx_excp_rcvd(struct atm_dev *dev) +{ +#if 0 /* closing the receiving size will cause too many excp int */ + IADEV *iadev; + u_short state; + u_short excpq_rd_ptr; + //u_short *ptr; + int vci, error = 1; + iadev = INPH_IA_DEV(dev); + state = readl(iadev->reass_reg + STATE_REG) & 0xffff; + while((state & EXCPQ_EMPTY) != EXCPQ_EMPTY) + { printk("state = %x \n", state); + excpq_rd_ptr = readw(iadev->reass_reg + EXCP_Q_RD_PTR) & 0xffff; + printk("state = %x excpq_rd_ptr = %x \n", state, excpq_rd_ptr); + if (excpq_rd_ptr == *(u16*)(iadev->reass_reg + EXCP_Q_WR_PTR)) + IF_ERR(printk("excpq_rd_ptr is wrong!!!\n");) + // TODO: update exception stat + vci = readw(iadev->reass_ram+excpq_rd_ptr); + error = readw(iadev->reass_ram+excpq_rd_ptr+2) & 0x0007; + // pwang_test + excpq_rd_ptr += 4; + if (excpq_rd_ptr > (readw(iadev->reass_reg + EXCP_Q_ED_ADR)& 0xffff)) + excpq_rd_ptr = readw(iadev->reass_reg + EXCP_Q_ST_ADR)& 0xffff; + writew( excpq_rd_ptr, iadev->reass_reg + EXCP_Q_RD_PTR); + state = readl(iadev->reass_reg + STATE_REG) & 0xffff; + } +#endif +} + +static void free_desc(struct atm_dev *dev, int desc) +{ + IADEV *iadev; + iadev = INPH_IA_DEV(dev); + writew(desc, iadev->reass_ram+iadev->rfL.fdq_wr); + iadev->rfL.fdq_wr +=2; + if (iadev->rfL.fdq_wr > iadev->rfL.fdq_ed) + iadev->rfL.fdq_wr = iadev->rfL.fdq_st; + writew(iadev->rfL.fdq_wr, iadev->reass_reg+FREEQ_WR_PTR); +} + + +static int rx_pkt(struct atm_dev *dev) +{ + IADEV *iadev; + struct atm_vcc *vcc; + unsigned short status; + struct rx_buf_desc __iomem *buf_desc_ptr; + int desc; + struct dle* wr_ptr; + int len; + struct sk_buff *skb; + u_int buf_addr, dma_addr; + + iadev = INPH_IA_DEV(dev); + if (iadev->rfL.pcq_rd == (readw(iadev->reass_reg+PCQ_WR_PTR)&0xffff)) + { + printk(KERN_ERR DEV_LABEL "(itf %d) Receive queue empty\n", dev->number); + return -EINVAL; + } + /* mask 1st 3 bits to get the actual descno. */ + desc = readw(iadev->reass_ram+iadev->rfL.pcq_rd) & 0x1fff; + IF_RX(printk("reass_ram = %p iadev->rfL.pcq_rd = 0x%x desc = %d\n", + iadev->reass_ram, iadev->rfL.pcq_rd, desc); + printk(" pcq_wr_ptr = 0x%x\n", + readw(iadev->reass_reg+PCQ_WR_PTR)&0xffff);) + /* update the read pointer - maybe we shud do this in the end*/ + if ( iadev->rfL.pcq_rd== iadev->rfL.pcq_ed) + iadev->rfL.pcq_rd = iadev->rfL.pcq_st; + else + iadev->rfL.pcq_rd += 2; + writew(iadev->rfL.pcq_rd, iadev->reass_reg+PCQ_RD_PTR); + + /* get the buffer desc entry. + update stuff. - doesn't seem to be any update necessary + */ + buf_desc_ptr = iadev->RX_DESC_BASE_ADDR; + /* make the ptr point to the corresponding buffer desc entry */ + buf_desc_ptr += desc; + if (!desc || (desc > iadev->num_rx_desc) || + ((buf_desc_ptr->vc_index & 0xffff) >= iadev->num_vc)) { + free_desc(dev, desc); + IF_ERR(printk("IA: bad descriptor desc = %d \n", desc);) + return -1; + } + vcc = iadev->rx_open[buf_desc_ptr->vc_index & 0xffff]; + if (!vcc) + { + free_desc(dev, desc); + printk("IA: null vcc, drop PDU\n"); + return -1; + } + + + /* might want to check the status bits for errors */ + status = (u_short) (buf_desc_ptr->desc_mode); + if (status & (RX_CER | RX_PTE | RX_OFL)) + { + atomic_inc(&vcc->stats->rx_err); + IF_ERR(printk("IA: bad packet, dropping it");) + if (status & RX_CER) { + IF_ERR(printk(" cause: packet CRC error\n");) + } + else if (status & RX_PTE) { + IF_ERR(printk(" cause: packet time out\n");) + } + else { + IF_ERR(printk(" cause: buffer overflow\n");) + } + goto out_free_desc; + } + + /* + build DLE. + */ + + buf_addr = (buf_desc_ptr->buf_start_hi << 16) | buf_desc_ptr->buf_start_lo; + dma_addr = (buf_desc_ptr->dma_start_hi << 16) | buf_desc_ptr->dma_start_lo; + len = dma_addr - buf_addr; + if (len > iadev->rx_buf_sz) { + printk("Over %d bytes sdu received, dropped!!!\n", iadev->rx_buf_sz); + atomic_inc(&vcc->stats->rx_err); + goto out_free_desc; + } + + if (!(skb = atm_alloc_charge(vcc, len, GFP_ATOMIC))) { + if (vcc->vci < 32) + printk("Drop control packets\n"); + goto out_free_desc; + } + skb_put(skb,len); + // pwang_test + ATM_SKB(skb)->vcc = vcc; + ATM_DESC(skb) = desc; + skb_queue_tail(&iadev->rx_dma_q, skb); + + /* Build the DLE structure */ + wr_ptr = iadev->rx_dle_q.write; + wr_ptr->sys_pkt_addr = dma_map_single(&iadev->pci->dev, skb->data, + len, DMA_FROM_DEVICE); + wr_ptr->local_pkt_addr = buf_addr; + wr_ptr->bytes = len; /* We don't know this do we ?? */ + wr_ptr->mode = DMA_INT_ENABLE; + + /* shud take care of wrap around here too. */ + if(++wr_ptr == iadev->rx_dle_q.end) + wr_ptr = iadev->rx_dle_q.start; + iadev->rx_dle_q.write = wr_ptr; + udelay(1); + /* Increment transaction counter */ + writel(1, iadev->dma+IPHASE5575_RX_COUNTER); +out: return 0; +out_free_desc: + free_desc(dev, desc); + goto out; +} + +static void rx_intr(struct atm_dev *dev) +{ + IADEV *iadev; + u_short status; + u_short state, i; + + iadev = INPH_IA_DEV(dev); + status = readl(iadev->reass_reg+REASS_INTR_STATUS_REG) & 0xffff; + IF_EVENT(printk("rx_intr: status = 0x%x\n", status);) + if (status & RX_PKT_RCVD) + { + /* do something */ + /* Basically recvd an interrupt for receiving a packet. + A descriptor would have been written to the packet complete + queue. Get all the descriptors and set up dma to move the + packets till the packet complete queue is empty.. + */ + state = readl(iadev->reass_reg + STATE_REG) & 0xffff; + IF_EVENT(printk("Rx intr status: RX_PKT_RCVD %08x\n", status);) + while(!(state & PCQ_EMPTY)) + { + rx_pkt(dev); + state = readl(iadev->reass_reg + STATE_REG) & 0xffff; + } + iadev->rxing = 1; + } + if (status & RX_FREEQ_EMPT) + { + if (iadev->rxing) { + iadev->rx_tmp_cnt = iadev->rx_pkt_cnt; + iadev->rx_tmp_jif = jiffies; + iadev->rxing = 0; + } + else if ((time_after(jiffies, iadev->rx_tmp_jif + 50)) && + ((iadev->rx_pkt_cnt - iadev->rx_tmp_cnt) == 0)) { + for (i = 1; i <= iadev->num_rx_desc; i++) + free_desc(dev, i); +printk("Test logic RUN!!!!\n"); + writew( ~(RX_FREEQ_EMPT|RX_EXCP_RCVD),iadev->reass_reg+REASS_MASK_REG); + iadev->rxing = 1; + } + IF_EVENT(printk("Rx intr status: RX_FREEQ_EMPT %08x\n", status);) + } + + if (status & RX_EXCP_RCVD) + { + /* probably need to handle the exception queue also. */ + IF_EVENT(printk("Rx intr status: RX_EXCP_RCVD %08x\n", status);) + rx_excp_rcvd(dev); + } + + + if (status & RX_RAW_RCVD) + { + /* need to handle the raw incoming cells. This deepnds on + whether we have programmed to receive the raw cells or not. + Else ignore. */ + IF_EVENT(printk("Rx intr status: RX_RAW_RCVD %08x\n", status);) + } +} + + +static void rx_dle_intr(struct atm_dev *dev) +{ + IADEV *iadev; + struct atm_vcc *vcc; + struct sk_buff *skb; + int desc; + u_short state; + struct dle *dle, *cur_dle; + u_int dle_lp; + int len; + iadev = INPH_IA_DEV(dev); + + /* free all the dles done, that is just update our own dle read pointer + - do we really need to do this. Think not. */ + /* DMA is done, just get all the recevie buffers from the rx dma queue + and push them up to the higher layer protocol. Also free the desc + associated with the buffer. */ + dle = iadev->rx_dle_q.read; + dle_lp = readl(iadev->dma+IPHASE5575_RX_LIST_ADDR) & (sizeof(struct dle)*DLE_ENTRIES - 1); + cur_dle = (struct dle*)(iadev->rx_dle_q.start + (dle_lp >> 4)); + while(dle != cur_dle) + { + /* free the DMAed skb */ + skb = skb_dequeue(&iadev->rx_dma_q); + if (!skb) + goto INCR_DLE; + desc = ATM_DESC(skb); + free_desc(dev, desc); + + if (!(len = skb->len)) + { + printk("rx_dle_intr: skb len 0\n"); + dev_kfree_skb_any(skb); + } + else + { + struct cpcs_trailer *trailer; + u_short length; + struct ia_vcc *ia_vcc; + + dma_unmap_single(&iadev->pci->dev, iadev->rx_dle_q.write->sys_pkt_addr, + len, DMA_FROM_DEVICE); + /* no VCC related housekeeping done as yet. lets see */ + vcc = ATM_SKB(skb)->vcc; + if (!vcc) { + printk("IA: null vcc\n"); + dev_kfree_skb_any(skb); + goto INCR_DLE; + } + ia_vcc = INPH_IA_VCC(vcc); + if (ia_vcc == NULL) + { + atomic_inc(&vcc->stats->rx_err); + atm_return(vcc, skb->truesize); + dev_kfree_skb_any(skb); + goto INCR_DLE; + } + // get real pkt length pwang_test + trailer = (struct cpcs_trailer*)((u_char *)skb->data + + skb->len - sizeof(*trailer)); + length = swap_byte_order(trailer->length); + if ((length > iadev->rx_buf_sz) || (length > + (skb->len - sizeof(struct cpcs_trailer)))) + { + atomic_inc(&vcc->stats->rx_err); + IF_ERR(printk("rx_dle_intr: Bad AAL5 trailer %d (skb len %d)", + length, skb->len);) + atm_return(vcc, skb->truesize); + dev_kfree_skb_any(skb); + goto INCR_DLE; + } + skb_trim(skb, length); + + /* Display the packet */ + IF_RXPKT(printk("\nDmad Recvd data: len = %d \n", skb->len); + xdump(skb->data, skb->len, "RX: "); + printk("\n");) + + IF_RX(printk("rx_dle_intr: skb push");) + vcc->push(vcc,skb); + atomic_inc(&vcc->stats->rx); + iadev->rx_pkt_cnt++; + } +INCR_DLE: + if (++dle == iadev->rx_dle_q.end) + dle = iadev->rx_dle_q.start; + } + iadev->rx_dle_q.read = dle; + + /* if the interrupts are masked because there were no free desc available, + unmask them now. */ + if (!iadev->rxing) { + state = readl(iadev->reass_reg + STATE_REG) & 0xffff; + if (!(state & FREEQ_EMPTY)) { + state = readl(iadev->reass_reg + REASS_MASK_REG) & 0xffff; + writel(state & ~(RX_FREEQ_EMPT |/* RX_EXCP_RCVD |*/ RX_PKT_RCVD), + iadev->reass_reg+REASS_MASK_REG); + iadev->rxing++; + } + } +} + + +static int open_rx(struct atm_vcc *vcc) +{ + IADEV *iadev; + u_short __iomem *vc_table; + u_short __iomem *reass_ptr; + IF_EVENT(printk("iadev: open_rx %d.%d\n", vcc->vpi, vcc->vci);) + + if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0; + iadev = INPH_IA_DEV(vcc->dev); + if (vcc->qos.rxtp.traffic_class == ATM_ABR) { + if (iadev->phy_type & FE_25MBIT_PHY) { + printk("IA: ABR not support\n"); + return -EINVAL; + } + } + /* Make only this VCI in the vc table valid and let all + others be invalid entries */ + vc_table = iadev->reass_ram+RX_VC_TABLE*iadev->memSize; + vc_table += vcc->vci; + /* mask the last 6 bits and OR it with 3 for 1K VCs */ + + *vc_table = vcc->vci << 6; + /* Also keep a list of open rx vcs so that we can attach them with + incoming PDUs later. */ + if ((vcc->qos.rxtp.traffic_class == ATM_ABR) || + (vcc->qos.txtp.traffic_class == ATM_ABR)) + { + srv_cls_param_t srv_p; + init_abr_vc(iadev, &srv_p); + ia_open_abr_vc(iadev, &srv_p, vcc, 0); + } + else { /* for UBR later may need to add CBR logic */ + reass_ptr = iadev->reass_ram+REASS_TABLE*iadev->memSize; + reass_ptr += vcc->vci; + *reass_ptr = NO_AAL5_PKT; + } + + if (iadev->rx_open[vcc->vci]) + printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %d already open\n", + vcc->dev->number, vcc->vci); + iadev->rx_open[vcc->vci] = vcc; + return 0; +} + +static int rx_init(struct atm_dev *dev) +{ + IADEV *iadev; + struct rx_buf_desc __iomem *buf_desc_ptr; + unsigned long rx_pkt_start = 0; + void *dle_addr; + struct abr_vc_table *abr_vc_table; + u16 *vc_table; + u16 *reass_table; + int i,j, vcsize_sel; + u_short freeq_st_adr; + u_short *freeq_start; + + iadev = INPH_IA_DEV(dev); + // spin_lock_init(&iadev->rx_lock); + + /* Allocate 4k bytes - more aligned than needed (4k boundary) */ + dle_addr = dma_alloc_coherent(&iadev->pci->dev, DLE_TOTAL_SIZE, + &iadev->rx_dle_dma, GFP_KERNEL); + if (!dle_addr) { + printk(KERN_ERR DEV_LABEL "can't allocate DLEs\n"); + goto err_out; + } + iadev->rx_dle_q.start = (struct dle *)dle_addr; + iadev->rx_dle_q.read = iadev->rx_dle_q.start; + iadev->rx_dle_q.write = iadev->rx_dle_q.start; + iadev->rx_dle_q.end = (struct dle*)((unsigned long)dle_addr+sizeof(struct dle)*DLE_ENTRIES); + /* the end of the dle q points to the entry after the last + DLE that can be used. */ + + /* write the upper 20 bits of the start address to rx list address register */ + /* We know this is 32bit bus addressed so the following is safe */ + writel(iadev->rx_dle_dma & 0xfffff000, + iadev->dma + IPHASE5575_RX_LIST_ADDR); + IF_INIT(printk("Tx Dle list addr: 0x%p value: 0x%0x\n", + iadev->dma+IPHASE5575_TX_LIST_ADDR, + readl(iadev->dma + IPHASE5575_TX_LIST_ADDR)); + printk("Rx Dle list addr: 0x%p value: 0x%0x\n", + iadev->dma+IPHASE5575_RX_LIST_ADDR, + readl(iadev->dma + IPHASE5575_RX_LIST_ADDR));) + + writew(0xffff, iadev->reass_reg+REASS_MASK_REG); + writew(0, iadev->reass_reg+MODE_REG); + writew(RESET_REASS, iadev->reass_reg+REASS_COMMAND_REG); + + /* Receive side control memory map + ------------------------------- + + Buffer descr 0x0000 (736 - 23K) + VP Table 0x5c00 (256 - 512) + Except q 0x5e00 (128 - 512) + Free buffer q 0x6000 (1K - 2K) + Packet comp q 0x6800 (1K - 2K) + Reass Table 0x7000 (1K - 2K) + VC Table 0x7800 (1K - 2K) + ABR VC Table 0x8000 (1K - 32K) + */ + + /* Base address for Buffer Descriptor Table */ + writew(RX_DESC_BASE >> 16, iadev->reass_reg+REASS_DESC_BASE); + /* Set the buffer size register */ + writew(iadev->rx_buf_sz, iadev->reass_reg+BUF_SIZE); + + /* Initialize each entry in the Buffer Descriptor Table */ + iadev->RX_DESC_BASE_ADDR = iadev->reass_ram+RX_DESC_BASE*iadev->memSize; + buf_desc_ptr = iadev->RX_DESC_BASE_ADDR; + memset_io(buf_desc_ptr, 0, sizeof(*buf_desc_ptr)); + buf_desc_ptr++; + rx_pkt_start = iadev->rx_pkt_ram; + for(i=1; i<=iadev->num_rx_desc; i++) + { + memset_io(buf_desc_ptr, 0, sizeof(*buf_desc_ptr)); + buf_desc_ptr->buf_start_hi = rx_pkt_start >> 16; + buf_desc_ptr->buf_start_lo = rx_pkt_start & 0x0000ffff; + buf_desc_ptr++; + rx_pkt_start += iadev->rx_buf_sz; + } + IF_INIT(printk("Rx Buffer desc ptr: 0x%p\n", buf_desc_ptr);) + i = FREE_BUF_DESC_Q*iadev->memSize; + writew(i >> 16, iadev->reass_reg+REASS_QUEUE_BASE); + writew(i, iadev->reass_reg+FREEQ_ST_ADR); + writew(i+iadev->num_rx_desc*sizeof(u_short), + iadev->reass_reg+FREEQ_ED_ADR); + writew(i, iadev->reass_reg+FREEQ_RD_PTR); + writew(i+iadev->num_rx_desc*sizeof(u_short), + iadev->reass_reg+FREEQ_WR_PTR); + /* Fill the FREEQ with all the free descriptors. */ + freeq_st_adr = readw(iadev->reass_reg+FREEQ_ST_ADR); + freeq_start = (u_short *)(iadev->reass_ram+freeq_st_adr); + for(i=1; i<=iadev->num_rx_desc; i++) + { + *freeq_start = (u_short)i; + freeq_start++; + } + IF_INIT(printk("freeq_start: 0x%p\n", freeq_start);) + /* Packet Complete Queue */ + i = (PKT_COMP_Q * iadev->memSize) & 0xffff; + writew(i, iadev->reass_reg+PCQ_ST_ADR); + writew(i+iadev->num_vc*sizeof(u_short), iadev->reass_reg+PCQ_ED_ADR); + writew(i, iadev->reass_reg+PCQ_RD_PTR); + writew(i, iadev->reass_reg+PCQ_WR_PTR); + + /* Exception Queue */ + i = (EXCEPTION_Q * iadev->memSize) & 0xffff; + writew(i, iadev->reass_reg+EXCP_Q_ST_ADR); + writew(i + NUM_RX_EXCP * sizeof(RX_ERROR_Q), + iadev->reass_reg+EXCP_Q_ED_ADR); + writew(i, iadev->reass_reg+EXCP_Q_RD_PTR); + writew(i, iadev->reass_reg+EXCP_Q_WR_PTR); + + /* Load local copy of FREEQ and PCQ ptrs */ + iadev->rfL.fdq_st = readw(iadev->reass_reg+FREEQ_ST_ADR) & 0xffff; + iadev->rfL.fdq_ed = readw(iadev->reass_reg+FREEQ_ED_ADR) & 0xffff ; + iadev->rfL.fdq_rd = readw(iadev->reass_reg+FREEQ_RD_PTR) & 0xffff; + iadev->rfL.fdq_wr = readw(iadev->reass_reg+FREEQ_WR_PTR) & 0xffff; + iadev->rfL.pcq_st = readw(iadev->reass_reg+PCQ_ST_ADR) & 0xffff; + iadev->rfL.pcq_ed = readw(iadev->reass_reg+PCQ_ED_ADR) & 0xffff; + iadev->rfL.pcq_rd = readw(iadev->reass_reg+PCQ_RD_PTR) & 0xffff; + iadev->rfL.pcq_wr = readw(iadev->reass_reg+PCQ_WR_PTR) & 0xffff; + + IF_INIT(printk("INIT:pcq_st:0x%x pcq_ed:0x%x pcq_rd:0x%x pcq_wr:0x%x", + iadev->rfL.pcq_st, iadev->rfL.pcq_ed, iadev->rfL.pcq_rd, + iadev->rfL.pcq_wr);) + /* just for check - no VP TBL */ + /* VP Table */ + /* writew(0x0b80, iadev->reass_reg+VP_LKUP_BASE); */ + /* initialize VP Table for invalid VPIs + - I guess we can write all 1s or 0x000f in the entire memory + space or something similar. + */ + + /* This seems to work and looks right to me too !!! */ + i = REASS_TABLE * iadev->memSize; + writew((i >> 3), iadev->reass_reg+REASS_TABLE_BASE); + /* initialize Reassembly table to I don't know what ???? */ + reass_table = (u16 *)(iadev->reass_ram+i); + j = REASS_TABLE_SZ * iadev->memSize; + for(i=0; i < j; i++) + *reass_table++ = NO_AAL5_PKT; + i = 8*1024; + vcsize_sel = 0; + while (i != iadev->num_vc) { + i /= 2; + vcsize_sel++; + } + i = RX_VC_TABLE * iadev->memSize; + writew(((i>>3) & 0xfff8) | vcsize_sel, iadev->reass_reg+VC_LKUP_BASE); + vc_table = (u16 *)(iadev->reass_ram+RX_VC_TABLE*iadev->memSize); + j = RX_VC_TABLE_SZ * iadev->memSize; + for(i = 0; i < j; i++) + { + /* shift the reassembly pointer by 3 + lower 3 bits of + vc_lkup_base register (=3 for 1K VCs) and the last byte + is those low 3 bits. + Shall program this later. + */ + *vc_table = (i << 6) | 15; /* for invalid VCI */ + vc_table++; + } + /* ABR VC table */ + i = ABR_VC_TABLE * iadev->memSize; + writew(i >> 3, iadev->reass_reg+ABR_LKUP_BASE); + + i = ABR_VC_TABLE * iadev->memSize; + abr_vc_table = (struct abr_vc_table *)(iadev->reass_ram+i); + j = REASS_TABLE_SZ * iadev->memSize; + memset ((char*)abr_vc_table, 0, j * sizeof(*abr_vc_table)); + for(i = 0; i < j; i++) { + abr_vc_table->rdf = 0x0003; + abr_vc_table->air = 0x5eb1; + abr_vc_table++; + } + + /* Initialize other registers */ + + /* VP Filter Register set for VC Reassembly only */ + writew(0xff00, iadev->reass_reg+VP_FILTER); + writew(0, iadev->reass_reg+XTRA_RM_OFFSET); + writew(0x1, iadev->reass_reg+PROTOCOL_ID); + + /* Packet Timeout Count related Registers : + Set packet timeout to occur in about 3 seconds + Set Packet Aging Interval count register to overflow in about 4 us + */ + writew(0xF6F8, iadev->reass_reg+PKT_TM_CNT ); + + i = (j >> 6) & 0xFF; + j += 2 * (j - 1); + i |= ((j << 2) & 0xFF00); + writew(i, iadev->reass_reg+TMOUT_RANGE); + + /* initiate the desc_tble */ + for(i=0; inum_tx_desc;i++) + iadev->desc_tbl[i].timestamp = 0; + + /* to clear the interrupt status register - read it */ + readw(iadev->reass_reg+REASS_INTR_STATUS_REG); + + /* Mask Register - clear it */ + writew(~(RX_FREEQ_EMPT|RX_PKT_RCVD), iadev->reass_reg+REASS_MASK_REG); + + skb_queue_head_init(&iadev->rx_dma_q); + iadev->rx_free_desc_qhead = NULL; + + iadev->rx_open = kcalloc(iadev->num_vc, sizeof(void *), GFP_KERNEL); + if (!iadev->rx_open) { + printk(KERN_ERR DEV_LABEL "itf %d couldn't get free page\n", + dev->number); + goto err_free_dle; + } + + iadev->rxing = 1; + iadev->rx_pkt_cnt = 0; + /* Mode Register */ + writew(R_ONLINE, iadev->reass_reg+MODE_REG); + return 0; + +err_free_dle: + dma_free_coherent(&iadev->pci->dev, DLE_TOTAL_SIZE, iadev->rx_dle_q.start, + iadev->rx_dle_dma); +err_out: + return -ENOMEM; +} + + +/* + The memory map suggested in appendix A and the coding for it. + Keeping it around just in case we change our mind later. + + Buffer descr 0x0000 (128 - 4K) + UBR sched 0x1000 (1K - 4K) + UBR Wait q 0x2000 (1K - 4K) + Commn queues 0x3000 Packet Ready, Trasmit comp(0x3100) + (128 - 256) each + extended VC 0x4000 (1K - 8K) + ABR sched 0x6000 and ABR wait queue (1K - 2K) each + CBR sched 0x7000 (as needed) + VC table 0x8000 (1K - 32K) +*/ + +static void tx_intr(struct atm_dev *dev) +{ + IADEV *iadev; + unsigned short status; + unsigned long flags; + + iadev = INPH_IA_DEV(dev); + + status = readl(iadev->seg_reg+SEG_INTR_STATUS_REG); + if (status & TRANSMIT_DONE){ + + IF_EVENT(printk("Transmit Done Intr logic run\n");) + spin_lock_irqsave(&iadev->tx_lock, flags); + ia_tx_poll(iadev); + spin_unlock_irqrestore(&iadev->tx_lock, flags); + writew(TRANSMIT_DONE, iadev->seg_reg+SEG_INTR_STATUS_REG); + if (iadev->close_pending) + wake_up(&iadev->close_wait); + } + if (status & TCQ_NOT_EMPTY) + { + IF_EVENT(printk("TCQ_NOT_EMPTY int received\n");) + } +} + +static void tx_dle_intr(struct atm_dev *dev) +{ + IADEV *iadev; + struct dle *dle, *cur_dle; + struct sk_buff *skb; + struct atm_vcc *vcc; + struct ia_vcc *iavcc; + u_int dle_lp; + unsigned long flags; + + iadev = INPH_IA_DEV(dev); + spin_lock_irqsave(&iadev->tx_lock, flags); + dle = iadev->tx_dle_q.read; + dle_lp = readl(iadev->dma+IPHASE5575_TX_LIST_ADDR) & + (sizeof(struct dle)*DLE_ENTRIES - 1); + cur_dle = (struct dle*)(iadev->tx_dle_q.start + (dle_lp >> 4)); + while (dle != cur_dle) + { + /* free the DMAed skb */ + skb = skb_dequeue(&iadev->tx_dma_q); + if (!skb) break; + + /* Revenge of the 2 dle (skb + trailer) used in ia_pkt_tx() */ + if (!((dle - iadev->tx_dle_q.start)%(2*sizeof(struct dle)))) { + dma_unmap_single(&iadev->pci->dev, dle->sys_pkt_addr, skb->len, + DMA_TO_DEVICE); + } + vcc = ATM_SKB(skb)->vcc; + if (!vcc) { + printk("tx_dle_intr: vcc is null\n"); + spin_unlock_irqrestore(&iadev->tx_lock, flags); + dev_kfree_skb_any(skb); + + return; + } + iavcc = INPH_IA_VCC(vcc); + if (!iavcc) { + printk("tx_dle_intr: iavcc is null\n"); + spin_unlock_irqrestore(&iadev->tx_lock, flags); + dev_kfree_skb_any(skb); + return; + } + if (vcc->qos.txtp.pcr >= iadev->rate_limit) { + if ((vcc->pop) && (skb->len != 0)) + { + vcc->pop(vcc, skb); + } + else { + dev_kfree_skb_any(skb); + } + } + else { /* Hold the rate-limited skb for flow control */ + IA_SKB_STATE(skb) |= IA_DLED; + skb_queue_tail(&iavcc->txing_skb, skb); + } + IF_EVENT(printk("tx_dle_intr: enque skb = 0x%p \n", skb);) + if (++dle == iadev->tx_dle_q.end) + dle = iadev->tx_dle_q.start; + } + iadev->tx_dle_q.read = dle; + spin_unlock_irqrestore(&iadev->tx_lock, flags); +} + +static int open_tx(struct atm_vcc *vcc) +{ + struct ia_vcc *ia_vcc; + IADEV *iadev; + struct main_vc *vc; + struct ext_vc *evc; + int ret; + IF_EVENT(printk("iadev: open_tx entered vcc->vci = %d\n", vcc->vci);) + if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0; + iadev = INPH_IA_DEV(vcc->dev); + + if (iadev->phy_type & FE_25MBIT_PHY) { + if (vcc->qos.txtp.traffic_class == ATM_ABR) { + printk("IA: ABR not support\n"); + return -EINVAL; + } + if (vcc->qos.txtp.traffic_class == ATM_CBR) { + printk("IA: CBR not support\n"); + return -EINVAL; + } + } + ia_vcc = INPH_IA_VCC(vcc); + memset((caddr_t)ia_vcc, 0, sizeof(*ia_vcc)); + if (vcc->qos.txtp.max_sdu > + (iadev->tx_buf_sz - sizeof(struct cpcs_trailer))){ + printk("IA: SDU size over (%d) the configured SDU size %d\n", + vcc->qos.txtp.max_sdu,iadev->tx_buf_sz); + vcc->dev_data = NULL; + kfree(ia_vcc); + return -EINVAL; + } + ia_vcc->vc_desc_cnt = 0; + ia_vcc->txing = 1; + + /* find pcr */ + if (vcc->qos.txtp.max_pcr == ATM_MAX_PCR) + vcc->qos.txtp.pcr = iadev->LineRate; + else if ((vcc->qos.txtp.max_pcr == 0)&&( vcc->qos.txtp.pcr <= 0)) + vcc->qos.txtp.pcr = iadev->LineRate; + else if ((vcc->qos.txtp.max_pcr > vcc->qos.txtp.pcr) && (vcc->qos.txtp.max_pcr> 0)) + vcc->qos.txtp.pcr = vcc->qos.txtp.max_pcr; + if (vcc->qos.txtp.pcr > iadev->LineRate) + vcc->qos.txtp.pcr = iadev->LineRate; + ia_vcc->pcr = vcc->qos.txtp.pcr; + + if (ia_vcc->pcr > (iadev->LineRate / 6) ) ia_vcc->ltimeout = HZ / 10; + else if (ia_vcc->pcr > (iadev->LineRate / 130)) ia_vcc->ltimeout = HZ; + else if (ia_vcc->pcr <= 170) ia_vcc->ltimeout = 16 * HZ; + else ia_vcc->ltimeout = 2700 * HZ / ia_vcc->pcr; + if (ia_vcc->pcr < iadev->rate_limit) + skb_queue_head_init (&ia_vcc->txing_skb); + if (ia_vcc->pcr < iadev->rate_limit) { + struct sock *sk = sk_atm(vcc); + + if (vcc->qos.txtp.max_sdu != 0) { + if (ia_vcc->pcr > 60000) + sk->sk_sndbuf = vcc->qos.txtp.max_sdu * 5; + else if (ia_vcc->pcr > 2000) + sk->sk_sndbuf = vcc->qos.txtp.max_sdu * 4; + else + sk->sk_sndbuf = vcc->qos.txtp.max_sdu * 3; + } + else + sk->sk_sndbuf = 24576; + } + + vc = (struct main_vc *)iadev->MAIN_VC_TABLE_ADDR; + evc = (struct ext_vc *)iadev->EXT_VC_TABLE_ADDR; + vc += vcc->vci; + evc += vcc->vci; + memset((caddr_t)vc, 0, sizeof(*vc)); + memset((caddr_t)evc, 0, sizeof(*evc)); + + /* store the most significant 4 bits of vci as the last 4 bits + of first part of atm header. + store the last 12 bits of vci as first 12 bits of the second + part of the atm header. + */ + evc->atm_hdr1 = (vcc->vci >> 12) & 0x000f; + evc->atm_hdr2 = (vcc->vci & 0x0fff) << 4; + + /* check the following for different traffic classes */ + if (vcc->qos.txtp.traffic_class == ATM_UBR) + { + vc->type = UBR; + vc->status = CRC_APPEND; + vc->acr = cellrate_to_float(iadev->LineRate); + if (vcc->qos.txtp.pcr > 0) + vc->acr = cellrate_to_float(vcc->qos.txtp.pcr); + IF_UBR(printk("UBR: txtp.pcr = 0x%x f_rate = 0x%x\n", + vcc->qos.txtp.max_pcr,vc->acr);) + } + else if (vcc->qos.txtp.traffic_class == ATM_ABR) + { srv_cls_param_t srv_p; + IF_ABR(printk("Tx ABR VCC\n");) + init_abr_vc(iadev, &srv_p); + if (vcc->qos.txtp.pcr > 0) + srv_p.pcr = vcc->qos.txtp.pcr; + if (vcc->qos.txtp.min_pcr > 0) { + int tmpsum = iadev->sum_mcr+iadev->sum_cbr+vcc->qos.txtp.min_pcr; + if (tmpsum > iadev->LineRate) + return -EBUSY; + srv_p.mcr = vcc->qos.txtp.min_pcr; + iadev->sum_mcr += vcc->qos.txtp.min_pcr; + } + else srv_p.mcr = 0; + if (vcc->qos.txtp.icr) + srv_p.icr = vcc->qos.txtp.icr; + if (vcc->qos.txtp.tbe) + srv_p.tbe = vcc->qos.txtp.tbe; + if (vcc->qos.txtp.frtt) + srv_p.frtt = vcc->qos.txtp.frtt; + if (vcc->qos.txtp.rif) + srv_p.rif = vcc->qos.txtp.rif; + if (vcc->qos.txtp.rdf) + srv_p.rdf = vcc->qos.txtp.rdf; + if (vcc->qos.txtp.nrm_pres) + srv_p.nrm = vcc->qos.txtp.nrm; + if (vcc->qos.txtp.trm_pres) + srv_p.trm = vcc->qos.txtp.trm; + if (vcc->qos.txtp.adtf_pres) + srv_p.adtf = vcc->qos.txtp.adtf; + if (vcc->qos.txtp.cdf_pres) + srv_p.cdf = vcc->qos.txtp.cdf; + if (srv_p.icr > srv_p.pcr) + srv_p.icr = srv_p.pcr; + IF_ABR(printk("ABR:vcc->qos.txtp.max_pcr = %d mcr = %d\n", + srv_p.pcr, srv_p.mcr);) + ia_open_abr_vc(iadev, &srv_p, vcc, 1); + } else if (vcc->qos.txtp.traffic_class == ATM_CBR) { + if (iadev->phy_type & FE_25MBIT_PHY) { + printk("IA: CBR not support\n"); + return -EINVAL; + } + if (vcc->qos.txtp.max_pcr > iadev->LineRate) { + IF_CBR(printk("PCR is not available\n");) + return -1; + } + vc->type = CBR; + vc->status = CRC_APPEND; + if ((ret = ia_cbr_setup (iadev, vcc)) < 0) { + return ret; + } + } else { + printk("iadev: Non UBR, ABR and CBR traffic not supported\n"); + } + + iadev->testTable[vcc->vci]->vc_status |= VC_ACTIVE; + IF_EVENT(printk("ia open_tx returning \n");) + return 0; +} + + +static int tx_init(struct atm_dev *dev) +{ + IADEV *iadev; + struct tx_buf_desc *buf_desc_ptr; + unsigned int tx_pkt_start; + void *dle_addr; + int i; + u_short tcq_st_adr; + u_short *tcq_start; + u_short prq_st_adr; + u_short *prq_start; + struct main_vc *vc; + struct ext_vc *evc; + u_short tmp16; + u32 vcsize_sel; + + iadev = INPH_IA_DEV(dev); + spin_lock_init(&iadev->tx_lock); + + IF_INIT(printk("Tx MASK REG: 0x%0x\n", + readw(iadev->seg_reg+SEG_MASK_REG));) + + /* Allocate 4k (boundary aligned) bytes */ + dle_addr = dma_alloc_coherent(&iadev->pci->dev, DLE_TOTAL_SIZE, + &iadev->tx_dle_dma, GFP_KERNEL); + if (!dle_addr) { + printk(KERN_ERR DEV_LABEL "can't allocate DLEs\n"); + goto err_out; + } + iadev->tx_dle_q.start = (struct dle*)dle_addr; + iadev->tx_dle_q.read = iadev->tx_dle_q.start; + iadev->tx_dle_q.write = iadev->tx_dle_q.start; + iadev->tx_dle_q.end = (struct dle*)((unsigned long)dle_addr+sizeof(struct dle)*DLE_ENTRIES); + + /* write the upper 20 bits of the start address to tx list address register */ + writel(iadev->tx_dle_dma & 0xfffff000, + iadev->dma + IPHASE5575_TX_LIST_ADDR); + writew(0xffff, iadev->seg_reg+SEG_MASK_REG); + writew(0, iadev->seg_reg+MODE_REG_0); + writew(RESET_SEG, iadev->seg_reg+SEG_COMMAND_REG); + iadev->MAIN_VC_TABLE_ADDR = iadev->seg_ram+MAIN_VC_TABLE*iadev->memSize; + iadev->EXT_VC_TABLE_ADDR = iadev->seg_ram+EXT_VC_TABLE*iadev->memSize; + iadev->ABR_SCHED_TABLE_ADDR=iadev->seg_ram+ABR_SCHED_TABLE*iadev->memSize; + + /* + Transmit side control memory map + -------------------------------- + Buffer descr 0x0000 (128 - 4K) + Commn queues 0x1000 Transmit comp, Packet ready(0x1400) + (512 - 1K) each + TCQ - 4K, PRQ - 5K + CBR Table 0x1800 (as needed) - 6K + UBR Table 0x3000 (1K - 4K) - 12K + UBR Wait queue 0x4000 (1K - 4K) - 16K + ABR sched 0x5000 and ABR wait queue (1K - 2K) each + ABR Tbl - 20K, ABR Wq - 22K + extended VC 0x6000 (1K - 8K) - 24K + VC Table 0x8000 (1K - 32K) - 32K + + Between 0x2000 (8K) and 0x3000 (12K) there is 4K space left for VBR Tbl + and Wait q, which can be allotted later. + */ + + /* Buffer Descriptor Table Base address */ + writew(TX_DESC_BASE, iadev->seg_reg+SEG_DESC_BASE); + + /* initialize each entry in the buffer descriptor table */ + buf_desc_ptr =(struct tx_buf_desc *)(iadev->seg_ram+TX_DESC_BASE); + memset((caddr_t)buf_desc_ptr, 0, sizeof(*buf_desc_ptr)); + buf_desc_ptr++; + tx_pkt_start = TX_PACKET_RAM; + for(i=1; i<=iadev->num_tx_desc; i++) + { + memset((caddr_t)buf_desc_ptr, 0, sizeof(*buf_desc_ptr)); + buf_desc_ptr->desc_mode = AAL5; + buf_desc_ptr->buf_start_hi = tx_pkt_start >> 16; + buf_desc_ptr->buf_start_lo = tx_pkt_start & 0x0000ffff; + buf_desc_ptr++; + tx_pkt_start += iadev->tx_buf_sz; + } + iadev->tx_buf = kmalloc_array(iadev->num_tx_desc, + sizeof(*iadev->tx_buf), + GFP_KERNEL); + if (!iadev->tx_buf) { + printk(KERN_ERR DEV_LABEL " couldn't get mem\n"); + goto err_free_dle; + } + for (i= 0; i< iadev->num_tx_desc; i++) + { + struct cpcs_trailer *cpcs; + + cpcs = kmalloc(sizeof(*cpcs), GFP_KERNEL|GFP_DMA); + if(!cpcs) { + printk(KERN_ERR DEV_LABEL " couldn't get freepage\n"); + goto err_free_tx_bufs; + } + iadev->tx_buf[i].cpcs = cpcs; + iadev->tx_buf[i].dma_addr = dma_map_single(&iadev->pci->dev, + cpcs, + sizeof(*cpcs), + DMA_TO_DEVICE); + } + iadev->desc_tbl = kmalloc_array(iadev->num_tx_desc, + sizeof(*iadev->desc_tbl), + GFP_KERNEL); + if (!iadev->desc_tbl) { + printk(KERN_ERR DEV_LABEL " couldn't get mem\n"); + goto err_free_all_tx_bufs; + } + + /* Communication Queues base address */ + i = TX_COMP_Q * iadev->memSize; + writew(i >> 16, iadev->seg_reg+SEG_QUEUE_BASE); + + /* Transmit Complete Queue */ + writew(i, iadev->seg_reg+TCQ_ST_ADR); + writew(i, iadev->seg_reg+TCQ_RD_PTR); + writew(i+iadev->num_tx_desc*sizeof(u_short),iadev->seg_reg+TCQ_WR_PTR); + iadev->host_tcq_wr = i + iadev->num_tx_desc*sizeof(u_short); + writew(i+2 * iadev->num_tx_desc * sizeof(u_short), + iadev->seg_reg+TCQ_ED_ADR); + /* Fill the TCQ with all the free descriptors. */ + tcq_st_adr = readw(iadev->seg_reg+TCQ_ST_ADR); + tcq_start = (u_short *)(iadev->seg_ram+tcq_st_adr); + for(i=1; i<=iadev->num_tx_desc; i++) + { + *tcq_start = (u_short)i; + tcq_start++; + } + + /* Packet Ready Queue */ + i = PKT_RDY_Q * iadev->memSize; + writew(i, iadev->seg_reg+PRQ_ST_ADR); + writew(i+2 * iadev->num_tx_desc * sizeof(u_short), + iadev->seg_reg+PRQ_ED_ADR); + writew(i, iadev->seg_reg+PRQ_RD_PTR); + writew(i, iadev->seg_reg+PRQ_WR_PTR); + + /* Load local copy of PRQ and TCQ ptrs */ + iadev->ffL.prq_st = readw(iadev->seg_reg+PRQ_ST_ADR) & 0xffff; + iadev->ffL.prq_ed = readw(iadev->seg_reg+PRQ_ED_ADR) & 0xffff; + iadev->ffL.prq_wr = readw(iadev->seg_reg+PRQ_WR_PTR) & 0xffff; + + iadev->ffL.tcq_st = readw(iadev->seg_reg+TCQ_ST_ADR) & 0xffff; + iadev->ffL.tcq_ed = readw(iadev->seg_reg+TCQ_ED_ADR) & 0xffff; + iadev->ffL.tcq_rd = readw(iadev->seg_reg+TCQ_RD_PTR) & 0xffff; + + /* Just for safety initializing the queue to have desc 1 always */ + /* Fill the PRQ with all the free descriptors. */ + prq_st_adr = readw(iadev->seg_reg+PRQ_ST_ADR); + prq_start = (u_short *)(iadev->seg_ram+prq_st_adr); + for(i=1; i<=iadev->num_tx_desc; i++) + { + *prq_start = (u_short)0; /* desc 1 in all entries */ + prq_start++; + } + /* CBR Table */ + IF_INIT(printk("Start CBR Init\n");) +#if 1 /* for 1K VC board, CBR_PTR_BASE is 0 */ + writew(0,iadev->seg_reg+CBR_PTR_BASE); +#else /* Charlie's logic is wrong ? */ + tmp16 = (iadev->seg_ram+CBR_SCHED_TABLE*iadev->memSize)>>17; + IF_INIT(printk("cbr_ptr_base = 0x%x ", tmp16);) + writew(tmp16,iadev->seg_reg+CBR_PTR_BASE); +#endif + + IF_INIT(printk("value in register = 0x%x\n", + readw(iadev->seg_reg+CBR_PTR_BASE));) + tmp16 = (CBR_SCHED_TABLE*iadev->memSize) >> 1; + writew(tmp16, iadev->seg_reg+CBR_TAB_BEG); + IF_INIT(printk("cbr_tab_beg = 0x%x in reg = 0x%x \n", tmp16, + readw(iadev->seg_reg+CBR_TAB_BEG));) + writew(tmp16, iadev->seg_reg+CBR_TAB_END+1); // CBR_PTR; + tmp16 = (CBR_SCHED_TABLE*iadev->memSize + iadev->num_vc*6 - 2) >> 1; + writew(tmp16, iadev->seg_reg+CBR_TAB_END); + IF_INIT(printk("iadev->seg_reg = 0x%p CBR_PTR_BASE = 0x%x\n", + iadev->seg_reg, readw(iadev->seg_reg+CBR_PTR_BASE));) + IF_INIT(printk("CBR_TAB_BEG = 0x%x, CBR_TAB_END = 0x%x, CBR_PTR = 0x%x\n", + readw(iadev->seg_reg+CBR_TAB_BEG), readw(iadev->seg_reg+CBR_TAB_END), + readw(iadev->seg_reg+CBR_TAB_END+1));) + + /* Initialize the CBR Schedualing Table */ + memset_io(iadev->seg_ram+CBR_SCHED_TABLE*iadev->memSize, + 0, iadev->num_vc*6); + iadev->CbrRemEntries = iadev->CbrTotEntries = iadev->num_vc*3; + iadev->CbrEntryPt = 0; + iadev->Granularity = MAX_ATM_155 / iadev->CbrTotEntries; + iadev->NumEnabledCBR = 0; + + /* UBR scheduling Table and wait queue */ + /* initialize all bytes of UBR scheduler table and wait queue to 0 + - SCHEDSZ is 1K (# of entries). + - UBR Table size is 4K + - UBR wait queue is 4K + since the table and wait queues are contiguous, all the bytes + can be initialized by one memeset. + */ + + vcsize_sel = 0; + i = 8*1024; + while (i != iadev->num_vc) { + i /= 2; + vcsize_sel++; + } + + i = MAIN_VC_TABLE * iadev->memSize; + writew(vcsize_sel | ((i >> 8) & 0xfff8),iadev->seg_reg+VCT_BASE); + i = EXT_VC_TABLE * iadev->memSize; + writew((i >> 8) & 0xfffe, iadev->seg_reg+VCTE_BASE); + i = UBR_SCHED_TABLE * iadev->memSize; + writew((i & 0xffff) >> 11, iadev->seg_reg+UBR_SBPTR_BASE); + i = UBR_WAIT_Q * iadev->memSize; + writew((i >> 7) & 0xffff, iadev->seg_reg+UBRWQ_BASE); + memset((caddr_t)(iadev->seg_ram+UBR_SCHED_TABLE*iadev->memSize), + 0, iadev->num_vc*8); + /* ABR scheduling Table(0x5000-0x57ff) and wait queue(0x5800-0x5fff)*/ + /* initialize all bytes of ABR scheduler table and wait queue to 0 + - SCHEDSZ is 1K (# of entries). + - ABR Table size is 2K + - ABR wait queue is 2K + since the table and wait queues are contiguous, all the bytes + can be initialized by one memeset. + */ + i = ABR_SCHED_TABLE * iadev->memSize; + writew((i >> 11) & 0xffff, iadev->seg_reg+ABR_SBPTR_BASE); + i = ABR_WAIT_Q * iadev->memSize; + writew((i >> 7) & 0xffff, iadev->seg_reg+ABRWQ_BASE); + + i = ABR_SCHED_TABLE*iadev->memSize; + memset((caddr_t)(iadev->seg_ram+i), 0, iadev->num_vc*4); + vc = (struct main_vc *)iadev->MAIN_VC_TABLE_ADDR; + evc = (struct ext_vc *)iadev->EXT_VC_TABLE_ADDR; + iadev->testTable = kmalloc_array(iadev->num_vc, + sizeof(*iadev->testTable), + GFP_KERNEL); + if (!iadev->testTable) { + printk("Get freepage failed\n"); + goto err_free_desc_tbl; + } + for(i=0; inum_vc; i++) + { + memset((caddr_t)vc, 0, sizeof(*vc)); + memset((caddr_t)evc, 0, sizeof(*evc)); + iadev->testTable[i] = kmalloc(sizeof(struct testTable_t), + GFP_KERNEL); + if (!iadev->testTable[i]) + goto err_free_test_tables; + iadev->testTable[i]->lastTime = 0; + iadev->testTable[i]->fract = 0; + iadev->testTable[i]->vc_status = VC_UBR; + vc++; + evc++; + } + + /* Other Initialization */ + + /* Max Rate Register */ + if (iadev->phy_type & FE_25MBIT_PHY) { + writew(RATE25, iadev->seg_reg+MAXRATE); + writew((UBR_EN | (0x23 << 2)), iadev->seg_reg+STPARMS); + } + else { + writew(cellrate_to_float(iadev->LineRate),iadev->seg_reg+MAXRATE); + writew((UBR_EN | ABR_EN | (0x23 << 2)), iadev->seg_reg+STPARMS); + } + /* Set Idle Header Reigisters to be sure */ + writew(0, iadev->seg_reg+IDLEHEADHI); + writew(0, iadev->seg_reg+IDLEHEADLO); + + /* Program ABR UBR Priority Register as PRI_ABR_UBR_EQUAL */ + writew(0xaa00, iadev->seg_reg+ABRUBR_ARB); + + iadev->close_pending = 0; + init_waitqueue_head(&iadev->close_wait); + init_waitqueue_head(&iadev->timeout_wait); + skb_queue_head_init(&iadev->tx_dma_q); + ia_init_rtn_q(&iadev->tx_return_q); + + /* RM Cell Protocol ID and Message Type */ + writew(RM_TYPE_4_0, iadev->seg_reg+RM_TYPE); + skb_queue_head_init (&iadev->tx_backlog); + + /* Mode Register 1 */ + writew(MODE_REG_1_VAL, iadev->seg_reg+MODE_REG_1); + + /* Mode Register 0 */ + writew(T_ONLINE, iadev->seg_reg+MODE_REG_0); + + /* Interrupt Status Register - read to clear */ + readw(iadev->seg_reg+SEG_INTR_STATUS_REG); + + /* Interrupt Mask Reg- don't mask TCQ_NOT_EMPTY interrupt generation */ + writew(~(TRANSMIT_DONE | TCQ_NOT_EMPTY), iadev->seg_reg+SEG_MASK_REG); + writew(TRANSMIT_DONE, iadev->seg_reg+SEG_INTR_STATUS_REG); + iadev->tx_pkt_cnt = 0; + iadev->rate_limit = iadev->LineRate / 3; + + return 0; + +err_free_test_tables: + while (--i >= 0) + kfree(iadev->testTable[i]); + kfree(iadev->testTable); +err_free_desc_tbl: + kfree(iadev->desc_tbl); +err_free_all_tx_bufs: + i = iadev->num_tx_desc; +err_free_tx_bufs: + while (--i >= 0) { + struct cpcs_trailer_desc *desc = iadev->tx_buf + i; + + dma_unmap_single(&iadev->pci->dev, desc->dma_addr, + sizeof(*desc->cpcs), DMA_TO_DEVICE); + kfree(desc->cpcs); + } + kfree(iadev->tx_buf); +err_free_dle: + dma_free_coherent(&iadev->pci->dev, DLE_TOTAL_SIZE, iadev->tx_dle_q.start, + iadev->tx_dle_dma); +err_out: + return -ENOMEM; +} + +static irqreturn_t ia_int(int irq, void *dev_id) +{ + struct atm_dev *dev; + IADEV *iadev; + unsigned int status; + int handled = 0; + + dev = dev_id; + iadev = INPH_IA_DEV(dev); + while( (status = readl(iadev->reg+IPHASE5575_BUS_STATUS_REG) & 0x7f)) + { + handled = 1; + IF_EVENT(printk("ia_int: status = 0x%x\n", status);) + if (status & STAT_REASSINT) + { + /* do something */ + IF_EVENT(printk("REASSINT Bus status reg: %08x\n", status);) + rx_intr(dev); + } + if (status & STAT_DLERINT) + { + /* Clear this bit by writing a 1 to it. */ + writel(STAT_DLERINT, iadev->reg + IPHASE5575_BUS_STATUS_REG); + rx_dle_intr(dev); + } + if (status & STAT_SEGINT) + { + /* do something */ + IF_EVENT(printk("IA: tx_intr \n");) + tx_intr(dev); + } + if (status & STAT_DLETINT) + { + writel(STAT_DLETINT, iadev->reg + IPHASE5575_BUS_STATUS_REG); + tx_dle_intr(dev); + } + if (status & (STAT_FEINT | STAT_ERRINT | STAT_MARKINT)) + { + if (status & STAT_FEINT) + ia_frontend_intr(iadev); + } + } + return IRQ_RETVAL(handled); +} + + + +/*----------------------------- entries --------------------------------*/ +static int get_esi(struct atm_dev *dev) +{ + IADEV *iadev; + int i; + u32 mac1; + u16 mac2; + + iadev = INPH_IA_DEV(dev); + mac1 = cpu_to_be32(le32_to_cpu(readl( + iadev->reg+IPHASE5575_MAC1))); + mac2 = cpu_to_be16(le16_to_cpu(readl(iadev->reg+IPHASE5575_MAC2))); + IF_INIT(printk("ESI: 0x%08x%04x\n", mac1, mac2);) + for (i=0; iesi[i] = mac1 >>(8*(MAC1_LEN-1-i)); + + for (i=0; iesi[i+MAC1_LEN] = mac2 >>(8*(MAC2_LEN - 1 -i)); + return 0; +} + +static int reset_sar(struct atm_dev *dev) +{ + IADEV *iadev; + int i, error; + unsigned int pci[64]; + + iadev = INPH_IA_DEV(dev); + for (i = 0; i < 64; i++) { + error = pci_read_config_dword(iadev->pci, i * 4, &pci[i]); + if (error != PCIBIOS_SUCCESSFUL) + return error; + } + writel(0, iadev->reg+IPHASE5575_EXT_RESET); + for (i = 0; i < 64; i++) { + error = pci_write_config_dword(iadev->pci, i * 4, pci[i]); + if (error != PCIBIOS_SUCCESSFUL) + return error; + } + udelay(5); + return 0; +} + + +static int ia_init(struct atm_dev *dev) +{ + IADEV *iadev; + unsigned long real_base; + void __iomem *base; + unsigned short command; + int error, i; + + /* The device has been identified and registered. Now we read + necessary configuration info like memory base address, + interrupt number etc */ + + IF_INIT(printk(">ia_init\n");) + dev->ci_range.vpi_bits = 0; + dev->ci_range.vci_bits = NR_VCI_LD; + + iadev = INPH_IA_DEV(dev); + real_base = pci_resource_start (iadev->pci, 0); + iadev->irq = iadev->pci->irq; + + error = pci_read_config_word(iadev->pci, PCI_COMMAND, &command); + if (error) { + printk(KERN_ERR DEV_LABEL "(itf %d): init error 0x%x\n", + dev->number,error); + return -EINVAL; + } + IF_INIT(printk(DEV_LABEL "(itf %d): rev.%d,realbase=0x%lx,irq=%d\n", + dev->number, iadev->pci->revision, real_base, iadev->irq);) + + /* find mapping size of board */ + + iadev->pci_map_size = pci_resource_len(iadev->pci, 0); + + if (iadev->pci_map_size == 0x100000){ + iadev->num_vc = 4096; + dev->ci_range.vci_bits = NR_VCI_4K_LD; + iadev->memSize = 4; + } + else if (iadev->pci_map_size == 0x40000) { + iadev->num_vc = 1024; + iadev->memSize = 1; + } + else { + printk("Unknown pci_map_size = 0x%x\n", iadev->pci_map_size); + return -EINVAL; + } + IF_INIT(printk (DEV_LABEL "map size: %i\n", iadev->pci_map_size);) + + /* enable bus mastering */ + pci_set_master(iadev->pci); + + /* + * Delay at least 1us before doing any mem accesses (how 'bout 10?) + */ + udelay(10); + + /* mapping the physical address to a virtual address in address space */ + base = ioremap(real_base,iadev->pci_map_size); /* ioremap is not resolved ??? */ + + if (!base) + { + printk(DEV_LABEL " (itf %d): can't set up page mapping\n", + dev->number); + return -ENOMEM; + } + IF_INIT(printk(DEV_LABEL " (itf %d): rev.%d,base=%p,irq=%d\n", + dev->number, iadev->pci->revision, base, iadev->irq);) + + /* filling the iphase dev structure */ + iadev->mem = iadev->pci_map_size /2; + iadev->real_base = real_base; + iadev->base = base; + + /* Bus Interface Control Registers */ + iadev->reg = base + REG_BASE; + /* Segmentation Control Registers */ + iadev->seg_reg = base + SEG_BASE; + /* Reassembly Control Registers */ + iadev->reass_reg = base + REASS_BASE; + /* Front end/ DMA control registers */ + iadev->phy = base + PHY_BASE; + iadev->dma = base + PHY_BASE; + /* RAM - Segmentation RAm and Reassembly RAM */ + iadev->ram = base + ACTUAL_RAM_BASE; + iadev->seg_ram = base + ACTUAL_SEG_RAM_BASE; + iadev->reass_ram = base + ACTUAL_REASS_RAM_BASE; + + /* lets print out the above */ + IF_INIT(printk("Base addrs: %p %p %p \n %p %p %p %p\n", + iadev->reg,iadev->seg_reg,iadev->reass_reg, + iadev->phy, iadev->ram, iadev->seg_ram, + iadev->reass_ram);) + + /* lets try reading the MAC address */ + error = get_esi(dev); + if (error) { + iounmap(iadev->base); + return error; + } + printk("IA: "); + for (i=0; i < ESI_LEN; i++) + printk("%s%02X",i ? "-" : "",dev->esi[i]); + printk("\n"); + + /* reset SAR */ + if (reset_sar(dev)) { + iounmap(iadev->base); + printk("IA: reset SAR fail, please try again\n"); + return 1; + } + return 0; +} + +static void ia_update_stats(IADEV *iadev) { + if (!iadev->carrier_detect) + return; + iadev->rx_cell_cnt += readw(iadev->reass_reg+CELL_CTR0)&0xffff; + iadev->rx_cell_cnt += (readw(iadev->reass_reg+CELL_CTR1) & 0xffff) << 16; + iadev->drop_rxpkt += readw(iadev->reass_reg + DRP_PKT_CNTR ) & 0xffff; + iadev->drop_rxcell += readw(iadev->reass_reg + ERR_CNTR) & 0xffff; + iadev->tx_cell_cnt += readw(iadev->seg_reg + CELL_CTR_LO_AUTO)&0xffff; + iadev->tx_cell_cnt += (readw(iadev->seg_reg+CELL_CTR_HIGH_AUTO)&0xffff)<<16; + return; +} + +static void ia_led_timer(struct timer_list *unused) { + unsigned long flags; + static u_char blinking[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + u_char i; + static u32 ctrl_reg; + for (i = 0; i < iadev_count; i++) { + if (ia_dev[i]) { + ctrl_reg = readl(ia_dev[i]->reg+IPHASE5575_BUS_CONTROL_REG); + if (blinking[i] == 0) { + blinking[i]++; + ctrl_reg &= (~CTRL_LED); + writel(ctrl_reg, ia_dev[i]->reg+IPHASE5575_BUS_CONTROL_REG); + ia_update_stats(ia_dev[i]); + } + else { + blinking[i] = 0; + ctrl_reg |= CTRL_LED; + writel(ctrl_reg, ia_dev[i]->reg+IPHASE5575_BUS_CONTROL_REG); + spin_lock_irqsave(&ia_dev[i]->tx_lock, flags); + if (ia_dev[i]->close_pending) + wake_up(&ia_dev[i]->close_wait); + ia_tx_poll(ia_dev[i]); + spin_unlock_irqrestore(&ia_dev[i]->tx_lock, flags); + } + } + } + mod_timer(&ia_timer, jiffies + HZ / 4); + return; +} + +static void ia_phy_put(struct atm_dev *dev, unsigned char value, + unsigned long addr) +{ + writel(value, INPH_IA_DEV(dev)->phy+addr); +} + +static unsigned char ia_phy_get(struct atm_dev *dev, unsigned long addr) +{ + return readl(INPH_IA_DEV(dev)->phy+addr); +} + +static void ia_free_tx(IADEV *iadev) +{ + int i; + + kfree(iadev->desc_tbl); + for (i = 0; i < iadev->num_vc; i++) + kfree(iadev->testTable[i]); + kfree(iadev->testTable); + for (i = 0; i < iadev->num_tx_desc; i++) { + struct cpcs_trailer_desc *desc = iadev->tx_buf + i; + + dma_unmap_single(&iadev->pci->dev, desc->dma_addr, + sizeof(*desc->cpcs), DMA_TO_DEVICE); + kfree(desc->cpcs); + } + kfree(iadev->tx_buf); + dma_free_coherent(&iadev->pci->dev, DLE_TOTAL_SIZE, iadev->tx_dle_q.start, + iadev->tx_dle_dma); +} + +static void ia_free_rx(IADEV *iadev) +{ + kfree(iadev->rx_open); + dma_free_coherent(&iadev->pci->dev, DLE_TOTAL_SIZE, iadev->rx_dle_q.start, + iadev->rx_dle_dma); +} + +static int ia_start(struct atm_dev *dev) +{ + IADEV *iadev; + int error; + unsigned char phy; + u32 ctrl_reg; + IF_EVENT(printk(">ia_start\n");) + iadev = INPH_IA_DEV(dev); + if (request_irq(iadev->irq, &ia_int, IRQF_SHARED, DEV_LABEL, dev)) { + printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n", + dev->number, iadev->irq); + error = -EAGAIN; + goto err_out; + } + /* @@@ should release IRQ on error */ + /* enabling memory + master */ + if ((error = pci_write_config_word(iadev->pci, + PCI_COMMAND, + PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER ))) + { + printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory+" + "master (0x%x)\n",dev->number, error); + error = -EIO; + goto err_free_irq; + } + udelay(10); + + /* Maybe we should reset the front end, initialize Bus Interface Control + Registers and see. */ + + IF_INIT(printk("Bus ctrl reg: %08x\n", + readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG));) + ctrl_reg = readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG); + ctrl_reg = (ctrl_reg & (CTRL_LED | CTRL_FE_RST)) + | CTRL_B8 + | CTRL_B16 + | CTRL_B32 + | CTRL_B48 + | CTRL_B64 + | CTRL_B128 + | CTRL_ERRMASK + | CTRL_DLETMASK /* shud be removed l8r */ + | CTRL_DLERMASK + | CTRL_SEGMASK + | CTRL_REASSMASK + | CTRL_FEMASK + | CTRL_CSPREEMPT; + + writel(ctrl_reg, iadev->reg+IPHASE5575_BUS_CONTROL_REG); + + IF_INIT(printk("Bus ctrl reg after initializing: %08x\n", + readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG)); + printk("Bus status reg after init: %08x\n", + readl(iadev->reg+IPHASE5575_BUS_STATUS_REG));) + + ia_hw_type(iadev); + error = tx_init(dev); + if (error) + goto err_free_irq; + error = rx_init(dev); + if (error) + goto err_free_tx; + + ctrl_reg = readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG); + writel(ctrl_reg | CTRL_FE_RST, iadev->reg+IPHASE5575_BUS_CONTROL_REG); + IF_INIT(printk("Bus ctrl reg after initializing: %08x\n", + readl(iadev->reg+IPHASE5575_BUS_CONTROL_REG));) + phy = 0; /* resolve compiler complaint */ + IF_INIT ( + if ((phy=ia_phy_get(dev,0)) == 0x30) + printk("IA: pm5346,rev.%d\n",phy&0x0f); + else + printk("IA: utopia,rev.%0x\n",phy);) + + if (iadev->phy_type & FE_25MBIT_PHY) + ia_mb25_init(iadev); + else if (iadev->phy_type & (FE_DS3_PHY | FE_E3_PHY)) + ia_suni_pm7345_init(iadev); + else { + error = suni_init(dev); + if (error) + goto err_free_rx; + if (dev->phy->start) { + error = dev->phy->start(dev); + if (error) + goto err_free_rx; + } + /* Get iadev->carrier_detect status */ + ia_frontend_intr(iadev); + } + return 0; + +err_free_rx: + ia_free_rx(iadev); +err_free_tx: + ia_free_tx(iadev); +err_free_irq: + free_irq(iadev->irq, dev); +err_out: + return error; +} + +static void ia_close(struct atm_vcc *vcc) +{ + DEFINE_WAIT(wait); + u16 *vc_table; + IADEV *iadev; + struct ia_vcc *ia_vcc; + struct sk_buff *skb = NULL; + struct sk_buff_head tmp_tx_backlog, tmp_vcc_backlog; + unsigned long closetime, flags; + + iadev = INPH_IA_DEV(vcc->dev); + ia_vcc = INPH_IA_VCC(vcc); + if (!ia_vcc) return; + + IF_EVENT(printk("ia_close: ia_vcc->vc_desc_cnt = %d vci = %d\n", + ia_vcc->vc_desc_cnt,vcc->vci);) + clear_bit(ATM_VF_READY,&vcc->flags); + skb_queue_head_init (&tmp_tx_backlog); + skb_queue_head_init (&tmp_vcc_backlog); + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + iadev->close_pending++; + prepare_to_wait(&iadev->timeout_wait, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(500)); + finish_wait(&iadev->timeout_wait, &wait); + spin_lock_irqsave(&iadev->tx_lock, flags); + while((skb = skb_dequeue(&iadev->tx_backlog))) { + if (ATM_SKB(skb)->vcc == vcc){ + if (vcc->pop) vcc->pop(vcc, skb); + else dev_kfree_skb_any(skb); + } + else + skb_queue_tail(&tmp_tx_backlog, skb); + } + while((skb = skb_dequeue(&tmp_tx_backlog))) + skb_queue_tail(&iadev->tx_backlog, skb); + IF_EVENT(printk("IA TX Done decs_cnt = %d\n", ia_vcc->vc_desc_cnt);) + closetime = 300000 / ia_vcc->pcr; + if (closetime == 0) + closetime = 1; + spin_unlock_irqrestore(&iadev->tx_lock, flags); + wait_event_timeout(iadev->close_wait, (ia_vcc->vc_desc_cnt <= 0), closetime); + spin_lock_irqsave(&iadev->tx_lock, flags); + iadev->close_pending--; + iadev->testTable[vcc->vci]->lastTime = 0; + iadev->testTable[vcc->vci]->fract = 0; + iadev->testTable[vcc->vci]->vc_status = VC_UBR; + if (vcc->qos.txtp.traffic_class == ATM_ABR) { + if (vcc->qos.txtp.min_pcr > 0) + iadev->sum_mcr -= vcc->qos.txtp.min_pcr; + } + if (vcc->qos.txtp.traffic_class == ATM_CBR) { + ia_vcc = INPH_IA_VCC(vcc); + iadev->sum_mcr -= ia_vcc->NumCbrEntry*iadev->Granularity; + ia_cbrVc_close (vcc); + } + spin_unlock_irqrestore(&iadev->tx_lock, flags); + } + + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + // reset reass table + vc_table = (u16 *)(iadev->reass_ram+REASS_TABLE*iadev->memSize); + vc_table += vcc->vci; + *vc_table = NO_AAL5_PKT; + // reset vc table + vc_table = (u16 *)(iadev->reass_ram+RX_VC_TABLE*iadev->memSize); + vc_table += vcc->vci; + *vc_table = (vcc->vci << 6) | 15; + if (vcc->qos.rxtp.traffic_class == ATM_ABR) { + struct abr_vc_table __iomem *abr_vc_table = + (iadev->reass_ram+ABR_VC_TABLE*iadev->memSize); + abr_vc_table += vcc->vci; + abr_vc_table->rdf = 0x0003; + abr_vc_table->air = 0x5eb1; + } + // Drain the packets + rx_dle_intr(vcc->dev); + iadev->rx_open[vcc->vci] = NULL; + } + kfree(INPH_IA_VCC(vcc)); + ia_vcc = NULL; + vcc->dev_data = NULL; + clear_bit(ATM_VF_ADDR,&vcc->flags); + return; +} + +static int ia_open(struct atm_vcc *vcc) +{ + struct ia_vcc *ia_vcc; + int error; + if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) + { + IF_EVENT(printk("ia: not partially allocated resources\n");) + vcc->dev_data = NULL; + } + if (vcc->vci != ATM_VPI_UNSPEC && vcc->vpi != ATM_VCI_UNSPEC) + { + IF_EVENT(printk("iphase open: unspec part\n");) + set_bit(ATM_VF_ADDR,&vcc->flags); + } + if (vcc->qos.aal != ATM_AAL5) + return -EINVAL; + IF_EVENT(printk(DEV_LABEL "(itf %d): open %d.%d\n", + vcc->dev->number, vcc->vpi, vcc->vci);) + + /* Device dependent initialization */ + ia_vcc = kmalloc(sizeof(*ia_vcc), GFP_KERNEL); + if (!ia_vcc) return -ENOMEM; + vcc->dev_data = ia_vcc; + + if ((error = open_rx(vcc))) + { + IF_EVENT(printk("iadev: error in open_rx, closing\n");) + ia_close(vcc); + return error; + } + + if ((error = open_tx(vcc))) + { + IF_EVENT(printk("iadev: error in open_tx, closing\n");) + ia_close(vcc); + return error; + } + + set_bit(ATM_VF_READY,&vcc->flags); + +#if 0 + { + static u8 first = 1; + if (first) { + ia_timer.expires = jiffies + 3*HZ; + add_timer(&ia_timer); + first = 0; + } + } +#endif + IF_EVENT(printk("ia open returning\n");) + return 0; +} + +static int ia_change_qos(struct atm_vcc *vcc, struct atm_qos *qos, int flags) +{ + IF_EVENT(printk(">ia_change_qos\n");) + return 0; +} + +static int ia_ioctl(struct atm_dev *dev, unsigned int cmd, void __user *arg) +{ + IA_CMDBUF ia_cmds; + IADEV *iadev; + int i, board; + u16 __user *tmps; + IF_EVENT(printk(">ia_ioctl\n");) + if (cmd != IA_CMD) { + if (!dev->phy->ioctl) return -EINVAL; + return dev->phy->ioctl(dev,cmd,arg); + } + if (copy_from_user(&ia_cmds, arg, sizeof ia_cmds)) return -EFAULT; + board = ia_cmds.status; + + if ((board < 0) || (board > iadev_count)) + board = 0; + board = array_index_nospec(board, iadev_count + 1); + + iadev = ia_dev[board]; + switch (ia_cmds.cmd) { + case MEMDUMP: + { + switch (ia_cmds.sub_cmd) { + case MEMDUMP_SEGREG: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + tmps = (u16 __user *)ia_cmds.buf; + for(i=0; i<0x80; i+=2, tmps++) + if(put_user((u16)(readl(iadev->seg_reg+i) & 0xffff), tmps)) return -EFAULT; + ia_cmds.status = 0; + ia_cmds.len = 0x80; + break; + case MEMDUMP_REASSREG: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + tmps = (u16 __user *)ia_cmds.buf; + for(i=0; i<0x80; i+=2, tmps++) + if(put_user((u16)(readl(iadev->reass_reg+i) & 0xffff), tmps)) return -EFAULT; + ia_cmds.status = 0; + ia_cmds.len = 0x80; + break; + case MEMDUMP_FFL: + { + ia_regs_t *regs_local; + ffredn_t *ffL; + rfredn_t *rfL; + + if (!capable(CAP_NET_ADMIN)) return -EPERM; + regs_local = kmalloc(sizeof(*regs_local), GFP_KERNEL); + if (!regs_local) return -ENOMEM; + ffL = ®s_local->ffredn; + rfL = ®s_local->rfredn; + /* Copy real rfred registers into the local copy */ + for (i=0; i<(sizeof (rfredn_t))/4; i++) + ((u_int *)rfL)[i] = readl(iadev->reass_reg + i) & 0xffff; + /* Copy real ffred registers into the local copy */ + for (i=0; i<(sizeof (ffredn_t))/4; i++) + ((u_int *)ffL)[i] = readl(iadev->seg_reg + i) & 0xffff; + + if (copy_to_user(ia_cmds.buf, regs_local,sizeof(ia_regs_t))) { + kfree(regs_local); + return -EFAULT; + } + kfree(regs_local); + printk("Board %d registers dumped\n", board); + ia_cmds.status = 0; + } + break; + case READ_REG: + { + if (!capable(CAP_NET_ADMIN)) return -EPERM; + desc_dbg(iadev); + ia_cmds.status = 0; + } + break; + case 0x6: + { + ia_cmds.status = 0; + printk("skb = 0x%p\n", skb_peek(&iadev->tx_backlog)); + printk("rtn_q: 0x%p\n",ia_deque_rtn_q(&iadev->tx_return_q)); + } + break; + case 0x8: + { + struct k_sonet_stats *stats; + stats = &PRIV(_ia_dev[board])->sonet_stats; + printk("section_bip: %d\n", atomic_read(&stats->section_bip)); + printk("line_bip : %d\n", atomic_read(&stats->line_bip)); + printk("path_bip : %d\n", atomic_read(&stats->path_bip)); + printk("line_febe : %d\n", atomic_read(&stats->line_febe)); + printk("path_febe : %d\n", atomic_read(&stats->path_febe)); + printk("corr_hcs : %d\n", atomic_read(&stats->corr_hcs)); + printk("uncorr_hcs : %d\n", atomic_read(&stats->uncorr_hcs)); + printk("tx_cells : %d\n", atomic_read(&stats->tx_cells)); + printk("rx_cells : %d\n", atomic_read(&stats->rx_cells)); + } + ia_cmds.status = 0; + break; + case 0x9: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + for (i = 1; i <= iadev->num_rx_desc; i++) + free_desc(_ia_dev[board], i); + writew( ~(RX_FREEQ_EMPT | RX_EXCP_RCVD), + iadev->reass_reg+REASS_MASK_REG); + iadev->rxing = 1; + + ia_cmds.status = 0; + break; + + case 0xb: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + ia_frontend_intr(iadev); + break; + case 0xa: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + { + ia_cmds.status = 0; + IADebugFlag = ia_cmds.maddr; + printk("New debug option loaded\n"); + } + break; + default: + ia_cmds.status = 0; + break; + } + } + break; + default: + break; + + } + return 0; +} + +static int ia_pkt_tx (struct atm_vcc *vcc, struct sk_buff *skb) { + IADEV *iadev; + struct dle *wr_ptr; + struct tx_buf_desc __iomem *buf_desc_ptr; + int desc; + int comp_code; + int total_len; + struct cpcs_trailer *trailer; + struct ia_vcc *iavcc; + + iadev = INPH_IA_DEV(vcc->dev); + iavcc = INPH_IA_VCC(vcc); + if (!iavcc->txing) { + printk("discard packet on closed VC\n"); + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + return 0; + } + + if (skb->len > iadev->tx_buf_sz - 8) { + printk("Transmit size over tx buffer size\n"); + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + return 0; + } + if ((unsigned long)skb->data & 3) { + printk("Misaligned SKB\n"); + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + return 0; + } + /* Get a descriptor number from our free descriptor queue + We get the descr number from the TCQ now, since I am using + the TCQ as a free buffer queue. Initially TCQ will be + initialized with all the descriptors and is hence, full. + */ + desc = get_desc (iadev, iavcc); + if (desc == 0xffff) + return 1; + comp_code = desc >> 13; + desc &= 0x1fff; + + if ((desc == 0) || (desc > iadev->num_tx_desc)) + { + IF_ERR(printk(DEV_LABEL "invalid desc for send: %d\n", desc);) + atomic_inc(&vcc->stats->tx); + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); + return 0; /* return SUCCESS */ + } + + if (comp_code) + { + IF_ERR(printk(DEV_LABEL "send desc:%d completion code %d error\n", + desc, comp_code);) + } + + /* remember the desc and vcc mapping */ + iavcc->vc_desc_cnt++; + iadev->desc_tbl[desc-1].iavcc = iavcc; + iadev->desc_tbl[desc-1].txskb = skb; + IA_SKB_STATE(skb) = 0; + + iadev->ffL.tcq_rd += 2; + if (iadev->ffL.tcq_rd > iadev->ffL.tcq_ed) + iadev->ffL.tcq_rd = iadev->ffL.tcq_st; + writew(iadev->ffL.tcq_rd, iadev->seg_reg+TCQ_RD_PTR); + + /* Put the descriptor number in the packet ready queue + and put the updated write pointer in the DLE field + */ + *(u16*)(iadev->seg_ram+iadev->ffL.prq_wr) = desc; + + iadev->ffL.prq_wr += 2; + if (iadev->ffL.prq_wr > iadev->ffL.prq_ed) + iadev->ffL.prq_wr = iadev->ffL.prq_st; + + /* Figure out the exact length of the packet and padding required to + make it aligned on a 48 byte boundary. */ + total_len = skb->len + sizeof(struct cpcs_trailer); + total_len = ((total_len + 47) / 48) * 48; + IF_TX(printk("ia packet len:%d padding:%d\n", total_len, total_len - skb->len);) + + /* Put the packet in a tx buffer */ + trailer = iadev->tx_buf[desc-1].cpcs; + IF_TX(printk("Sent: skb = 0x%p skb->data: 0x%p len: %d, desc: %d\n", + skb, skb->data, skb->len, desc);) + trailer->control = 0; + /*big endian*/ + trailer->length = ((skb->len & 0xff) << 8) | ((skb->len & 0xff00) >> 8); + trailer->crc32 = 0; /* not needed - dummy bytes */ + + /* Display the packet */ + IF_TXPKT(printk("Sent data: len = %d MsgNum = %d\n", + skb->len, tcnter++); + xdump(skb->data, skb->len, "TX: "); + printk("\n");) + + /* Build the buffer descriptor */ + buf_desc_ptr = iadev->seg_ram+TX_DESC_BASE; + buf_desc_ptr += desc; /* points to the corresponding entry */ + buf_desc_ptr->desc_mode = AAL5 | EOM_EN | APP_CRC32 | CMPL_INT; + /* Huh ? p.115 of users guide describes this as a read-only register */ + writew(TRANSMIT_DONE, iadev->seg_reg+SEG_INTR_STATUS_REG); + buf_desc_ptr->vc_index = vcc->vci; + buf_desc_ptr->bytes = total_len; + + if (vcc->qos.txtp.traffic_class == ATM_ABR) + clear_lockup (vcc, iadev); + + /* Build the DLE structure */ + wr_ptr = iadev->tx_dle_q.write; + memset((caddr_t)wr_ptr, 0, sizeof(*wr_ptr)); + wr_ptr->sys_pkt_addr = dma_map_single(&iadev->pci->dev, skb->data, + skb->len, DMA_TO_DEVICE); + wr_ptr->local_pkt_addr = (buf_desc_ptr->buf_start_hi << 16) | + buf_desc_ptr->buf_start_lo; + /* wr_ptr->bytes = swap_byte_order(total_len); didn't seem to affect?? */ + wr_ptr->bytes = skb->len; + + /* hw bug - DLEs of 0x2d, 0x2e, 0x2f cause DMA lockup */ + if ((wr_ptr->bytes >> 2) == 0xb) + wr_ptr->bytes = 0x30; + + wr_ptr->mode = TX_DLE_PSI; + wr_ptr->prq_wr_ptr_data = 0; + + /* end is not to be used for the DLE q */ + if (++wr_ptr == iadev->tx_dle_q.end) + wr_ptr = iadev->tx_dle_q.start; + + /* Build trailer dle */ + wr_ptr->sys_pkt_addr = iadev->tx_buf[desc-1].dma_addr; + wr_ptr->local_pkt_addr = ((buf_desc_ptr->buf_start_hi << 16) | + buf_desc_ptr->buf_start_lo) + total_len - sizeof(struct cpcs_trailer); + + wr_ptr->bytes = sizeof(struct cpcs_trailer); + wr_ptr->mode = DMA_INT_ENABLE; + wr_ptr->prq_wr_ptr_data = iadev->ffL.prq_wr; + + /* end is not to be used for the DLE q */ + if (++wr_ptr == iadev->tx_dle_q.end) + wr_ptr = iadev->tx_dle_q.start; + + iadev->tx_dle_q.write = wr_ptr; + ATM_DESC(skb) = vcc->vci; + skb_queue_tail(&iadev->tx_dma_q, skb); + + atomic_inc(&vcc->stats->tx); + iadev->tx_pkt_cnt++; + /* Increment transaction counter */ + writel(2, iadev->dma+IPHASE5575_TX_COUNTER); + +#if 0 + /* add flow control logic */ + if (atomic_read(&vcc->stats->tx) % 20 == 0) { + if (iavcc->vc_desc_cnt > 10) { + vcc->tx_quota = vcc->tx_quota * 3 / 4; + printk("Tx1: vcc->tx_quota = %d \n", (u32)vcc->tx_quota ); + iavcc->flow_inc = -1; + iavcc->saved_tx_quota = vcc->tx_quota; + } else if ((iavcc->flow_inc < 0) && (iavcc->vc_desc_cnt < 3)) { + // vcc->tx_quota = 3 * iavcc->saved_tx_quota / 4; + printk("Tx2: vcc->tx_quota = %d \n", (u32)vcc->tx_quota ); + iavcc->flow_inc = 0; + } + } +#endif + IF_TX(printk("ia send done\n");) + return 0; +} + +static int ia_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + IADEV *iadev; + unsigned long flags; + + iadev = INPH_IA_DEV(vcc->dev); + if ((!skb)||(skb->len>(iadev->tx_buf_sz-sizeof(struct cpcs_trailer)))) + { + if (!skb) + printk(KERN_CRIT "null skb in ia_send\n"); + else dev_kfree_skb_any(skb); + return -EINVAL; + } + spin_lock_irqsave(&iadev->tx_lock, flags); + if (!test_bit(ATM_VF_READY,&vcc->flags)){ + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&iadev->tx_lock, flags); + return -EINVAL; + } + ATM_SKB(skb)->vcc = vcc; + + if (skb_peek(&iadev->tx_backlog)) { + skb_queue_tail(&iadev->tx_backlog, skb); + } + else { + if (ia_pkt_tx (vcc, skb)) { + skb_queue_tail(&iadev->tx_backlog, skb); + } + } + spin_unlock_irqrestore(&iadev->tx_lock, flags); + return 0; + +} + +static int ia_proc_read(struct atm_dev *dev,loff_t *pos,char *page) +{ + int left = *pos, n; + char *tmpPtr; + IADEV *iadev = INPH_IA_DEV(dev); + if(!left--) { + if (iadev->phy_type == FE_25MBIT_PHY) { + n = sprintf(page, " Board Type : Iphase5525-1KVC-128K\n"); + return n; + } + if (iadev->phy_type == FE_DS3_PHY) + n = sprintf(page, " Board Type : Iphase-ATM-DS3"); + else if (iadev->phy_type == FE_E3_PHY) + n = sprintf(page, " Board Type : Iphase-ATM-E3"); + else if (iadev->phy_type == FE_UTP_OPTION) + n = sprintf(page, " Board Type : Iphase-ATM-UTP155"); + else + n = sprintf(page, " Board Type : Iphase-ATM-OC3"); + tmpPtr = page + n; + if (iadev->pci_map_size == 0x40000) + n += sprintf(tmpPtr, "-1KVC-"); + else + n += sprintf(tmpPtr, "-4KVC-"); + tmpPtr = page + n; + if ((iadev->memType & MEM_SIZE_MASK) == MEM_SIZE_1M) + n += sprintf(tmpPtr, "1M \n"); + else if ((iadev->memType & MEM_SIZE_MASK) == MEM_SIZE_512K) + n += sprintf(tmpPtr, "512K\n"); + else + n += sprintf(tmpPtr, "128K\n"); + return n; + } + if (!left) { + return sprintf(page, " Number of Tx Buffer: %u\n" + " Size of Tx Buffer : %u\n" + " Number of Rx Buffer: %u\n" + " Size of Rx Buffer : %u\n" + " Packets Received : %u\n" + " Packets Transmitted: %u\n" + " Cells Received : %u\n" + " Cells Transmitted : %u\n" + " Board Dropped Cells: %u\n" + " Board Dropped Pkts : %u\n", + iadev->num_tx_desc, iadev->tx_buf_sz, + iadev->num_rx_desc, iadev->rx_buf_sz, + iadev->rx_pkt_cnt, iadev->tx_pkt_cnt, + iadev->rx_cell_cnt, iadev->tx_cell_cnt, + iadev->drop_rxcell, iadev->drop_rxpkt); + } + return 0; +} + +static const struct atmdev_ops ops = { + .open = ia_open, + .close = ia_close, + .ioctl = ia_ioctl, + .send = ia_send, + .phy_put = ia_phy_put, + .phy_get = ia_phy_get, + .change_qos = ia_change_qos, + .proc_read = ia_proc_read, + .owner = THIS_MODULE, +}; + +static int ia_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct atm_dev *dev; + IADEV *iadev; + int ret; + + iadev = kzalloc(sizeof(*iadev), GFP_KERNEL); + if (!iadev) { + ret = -ENOMEM; + goto err_out; + } + + iadev->pci = pdev; + + IF_INIT(printk("ia detected at bus:%d dev: %d function:%d\n", + pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));) + if (pci_enable_device(pdev)) { + ret = -ENODEV; + goto err_out_free_iadev; + } + dev = atm_dev_register(DEV_LABEL, &pdev->dev, &ops, -1, NULL); + if (!dev) { + ret = -ENOMEM; + goto err_out_disable_dev; + } + dev->dev_data = iadev; + IF_INIT(printk(DEV_LABEL "registered at (itf :%d)\n", dev->number);) + IF_INIT(printk("dev_id = 0x%p iadev->LineRate = %d \n", dev, + iadev->LineRate);) + + pci_set_drvdata(pdev, dev); + + ia_dev[iadev_count] = iadev; + _ia_dev[iadev_count] = dev; + iadev_count++; + if (ia_init(dev) || ia_start(dev)) { + IF_INIT(printk("IA register failed!\n");) + iadev_count--; + ia_dev[iadev_count] = NULL; + _ia_dev[iadev_count] = NULL; + ret = -EINVAL; + goto err_out_deregister_dev; + } + IF_EVENT(printk("iadev_count = %d\n", iadev_count);) + + iadev->next_board = ia_boards; + ia_boards = dev; + + return 0; + +err_out_deregister_dev: + atm_dev_deregister(dev); +err_out_disable_dev: + pci_disable_device(pdev); +err_out_free_iadev: + kfree(iadev); +err_out: + return ret; +} + +static void ia_remove_one(struct pci_dev *pdev) +{ + struct atm_dev *dev = pci_get_drvdata(pdev); + IADEV *iadev = INPH_IA_DEV(dev); + + /* Disable phy interrupts */ + ia_phy_put(dev, ia_phy_get(dev, SUNI_RSOP_CIE) & ~(SUNI_RSOP_CIE_LOSE), + SUNI_RSOP_CIE); + udelay(1); + + if (dev->phy && dev->phy->stop) + dev->phy->stop(dev); + + /* De-register device */ + free_irq(iadev->irq, dev); + iadev_count--; + ia_dev[iadev_count] = NULL; + _ia_dev[iadev_count] = NULL; + IF_EVENT(printk("deregistering iav at (itf:%d)\n", dev->number);) + atm_dev_deregister(dev); + + iounmap(iadev->base); + pci_disable_device(pdev); + + ia_free_rx(iadev); + ia_free_tx(iadev); + + kfree(iadev); +} + +static const struct pci_device_id ia_pci_tbl[] = { + { PCI_VENDOR_ID_IPHASE, 0x0008, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_IPHASE, 0x0009, PCI_ANY_ID, PCI_ANY_ID, }, + { 0,} +}; +MODULE_DEVICE_TABLE(pci, ia_pci_tbl); + +static struct pci_driver ia_driver = { + .name = DEV_LABEL, + .id_table = ia_pci_tbl, + .probe = ia_init_one, + .remove = ia_remove_one, +}; + +static int __init ia_module_init(void) +{ + int ret; + + ret = pci_register_driver(&ia_driver); + if (ret >= 0) { + ia_timer.expires = jiffies + 3*HZ; + add_timer(&ia_timer); + } else + printk(KERN_ERR DEV_LABEL ": no adapter found\n"); + return ret; +} + +static void __exit ia_module_exit(void) +{ + pci_unregister_driver(&ia_driver); + + del_timer_sync(&ia_timer); +} + +module_init(ia_module_init); +module_exit(ia_module_exit); diff --git a/drivers/atm/iphase.h b/drivers/atm/iphase.h new file mode 100644 index 000000000..2beacf2fc --- /dev/null +++ b/drivers/atm/iphase.h @@ -0,0 +1,1453 @@ +/****************************************************************************** + Device driver for Interphase ATM PCI adapter cards + Author: Peter Wang + Interphase Corporation + Version: 1.0 + iphase.h: This is the header file for iphase.c. +******************************************************************************* + + This software may be used and distributed according to the terms + of the GNU General Public License (GPL), incorporated herein by reference. + Drivers based on this skeleton fall under the GPL and must retain + the authorship (implicit copyright) notice. + + 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. + + Modified from an incomplete driver for Interphase 5575 1KVC 1M card which + was originally written by Monalisa Agrawal at UNH. Now this driver + supports a variety of varients of Interphase ATM PCI (i)Chip adapter + card family (See www.iphase.com/products/ClassSheet.cfm?ClassID=ATM) + in terms of PHY type, the size of control memory and the size of + packet memory. The following are the change log and history: + + Bugfix the Mona's UBR driver. + Modify the basic memory allocation and dma logic. + Port the driver to the latest kernel from 2.0.46. + Complete the ABR logic of the driver, and added the ABR work- + around for the hardware anormalies. + Add the CBR support. + Add the flow control logic to the driver to allow rate-limit VC. + Add 4K VC support to the board with 512K control memory. + Add the support of all the variants of the Interphase ATM PCI + (i)Chip adapter cards including x575 (155M OC3 and UTP155), x525 + (25M UTP25) and x531 (DS3 and E3). + Add SMP support. + + Support and updates available at: ftp://ftp.iphase.com/pub/atm + +*******************************************************************************/ + +#ifndef IPHASE_H +#define IPHASE_H + + +/************************ IADBG DEFINE *********************************/ +/* IADebugFlag Bit Map */ +#define IF_IADBG_INIT_ADAPTER 0x00000001 // init adapter info +#define IF_IADBG_TX 0x00000002 // debug TX +#define IF_IADBG_RX 0x00000004 // debug RX +#define IF_IADBG_QUERY_INFO 0x00000008 // debug Request call +#define IF_IADBG_SHUTDOWN 0x00000010 // debug shutdown event +#define IF_IADBG_INTR 0x00000020 // debug interrupt DPC +#define IF_IADBG_TXPKT 0x00000040 // debug TX PKT +#define IF_IADBG_RXPKT 0x00000080 // debug RX PKT +#define IF_IADBG_ERR 0x00000100 // debug system error +#define IF_IADBG_EVENT 0x00000200 // debug event +#define IF_IADBG_DIS_INTR 0x00001000 // debug disable interrupt +#define IF_IADBG_EN_INTR 0x00002000 // debug enable interrupt +#define IF_IADBG_LOUD 0x00004000 // debugging info +#define IF_IADBG_VERY_LOUD 0x00008000 // excessive debugging info +#define IF_IADBG_CBR 0x00100000 // +#define IF_IADBG_UBR 0x00200000 // +#define IF_IADBG_ABR 0x00400000 // +#define IF_IADBG_DESC 0x01000000 // +#define IF_IADBG_SUNI_STAT 0x02000000 // suni statistics +#define IF_IADBG_RESET 0x04000000 + +#define IF_IADBG(f) if (IADebugFlag & (f)) + +#ifdef CONFIG_ATM_IA_DEBUG /* Debug build */ + +#define IF_LOUD(A) IF_IADBG(IF_IADBG_LOUD) { A } +#define IF_ERR(A) IF_IADBG(IF_IADBG_ERR) { A } +#define IF_VERY_LOUD(A) IF_IADBG( IF_IADBG_VERY_LOUD ) { A } + +#define IF_INIT_ADAPTER(A) IF_IADBG( IF_IADBG_INIT_ADAPTER ) { A } +#define IF_INIT(A) IF_IADBG( IF_IADBG_INIT_ADAPTER ) { A } +#define IF_SUNI_STAT(A) IF_IADBG( IF_IADBG_SUNI_STAT ) { A } +#define IF_QUERY_INFO(A) IF_IADBG( IF_IADBG_QUERY_INFO ) { A } +#define IF_COPY_OVER(A) IF_IADBG( IF_IADBG_COPY_OVER ) { A } + +#define IF_INTR(A) IF_IADBG( IF_IADBG_INTR ) { A } +#define IF_DIS_INTR(A) IF_IADBG( IF_IADBG_DIS_INTR ) { A } +#define IF_EN_INTR(A) IF_IADBG( IF_IADBG_EN_INTR ) { A } + +#define IF_TX(A) IF_IADBG( IF_IADBG_TX ) { A } +#define IF_RX(A) IF_IADBG( IF_IADBG_RX ) { A } +#define IF_TXPKT(A) IF_IADBG( IF_IADBG_TXPKT ) { A } +#define IF_RXPKT(A) IF_IADBG( IF_IADBG_RXPKT ) { A } + +#define IF_SHUTDOWN(A) IF_IADBG(IF_IADBG_SHUTDOWN) { A } +#define IF_CBR(A) IF_IADBG( IF_IADBG_CBR ) { A } +#define IF_UBR(A) IF_IADBG( IF_IADBG_UBR ) { A } +#define IF_ABR(A) IF_IADBG( IF_IADBG_ABR ) { A } +#define IF_EVENT(A) IF_IADBG( IF_IADBG_EVENT) { A } + +#else /* free build */ +#define IF_LOUD(A) +#define IF_VERY_LOUD(A) +#define IF_INIT_ADAPTER(A) +#define IF_INIT(A) +#define IF_SUNI_STAT(A) +#define IF_PVC_CHKPKT(A) +#define IF_QUERY_INFO(A) +#define IF_COPY_OVER(A) +#define IF_HANG(A) +#define IF_INTR(A) +#define IF_DIS_INTR(A) +#define IF_EN_INTR(A) +#define IF_TX(A) +#define IF_RX(A) +#define IF_TXDEBUG(A) +#define IF_VC(A) +#define IF_ERR(A) +#define IF_CBR(A) +#define IF_UBR(A) +#define IF_ABR(A) +#define IF_SHUTDOWN(A) +#define DbgPrint(A) +#define IF_EVENT(A) +#define IF_TXPKT(A) +#define IF_RXPKT(A) +#endif /* CONFIG_ATM_IA_DEBUG */ + +#define isprint(a) ((a >=' ')&&(a <= '~')) +#define ATM_DESC(skb) (skb->protocol) +#define IA_SKB_STATE(skb) (skb->protocol) +#define IA_DLED 1 +#define IA_TX_DONE 2 + +/* iadbg defines */ +#define IA_CMD 0x7749 +typedef struct { + int cmd; + int sub_cmd; + int len; + u32 maddr; + int status; + void __user *buf; +} IA_CMDBUF, *PIA_CMDBUF; + +/* cmds */ +#define MEMDUMP 0x01 + +/* sub_cmds */ +#define MEMDUMP_SEGREG 0x2 +#define MEMDUMP_DEV 0x1 +#define MEMDUMP_REASSREG 0x3 +#define MEMDUMP_FFL 0x4 +#define READ_REG 0x5 +#define WAKE_DBG_WAIT 0x6 + +/************************ IADBG DEFINE END ***************************/ + +#define Boolean(x) ((x) ? 1 : 0) +#define NR_VCI 1024 /* number of VCIs */ +#define NR_VCI_LD 10 /* log2(NR_VCI) */ +#define NR_VCI_4K 4096 /* number of VCIs */ +#define NR_VCI_4K_LD 12 /* log2(NR_VCI) */ +#define MEM_VALID 0xfffffff0 /* mask base address with this */ + +#ifndef PCI_VENDOR_ID_IPHASE +#define PCI_VENDOR_ID_IPHASE 0x107e +#endif +#ifndef PCI_DEVICE_ID_IPHASE_5575 +#define PCI_DEVICE_ID_IPHASE_5575 0x0008 +#endif +#define DEV_LABEL "ia" +#define PCR 207692 +#define ICR 100000 +#define MCR 0 +#define TBE 1000 +#define FRTT 1 +#define RIF 2 +#define RDF 4 +#define NRMCODE 5 /* 0 - 7 */ +#define TRMCODE 3 /* 0 - 7 */ +#define CDFCODE 6 +#define ATDFCODE 2 /* 0 - 15 */ + +/*---------------------- Packet/Cell Memory ------------------------*/ +#define TX_PACKET_RAM 0x00000 /* start of Trasnmit Packet memory - 0 */ +#define DFL_TX_BUF_SZ 10240 /* 10 K buffers */ +#define DFL_TX_BUFFERS 50 /* number of packet buffers for Tx + - descriptor 0 unused */ +#define REASS_RAM_SIZE 0x10000 /* for 64K 1K VC board */ +#define RX_PACKET_RAM 0x80000 /* start of Receive Packet memory - 512K */ +#define DFL_RX_BUF_SZ 10240 /* 10k buffers */ +#define DFL_RX_BUFFERS 50 /* number of packet buffers for Rx + - descriptor 0 unused */ + +struct cpcs_trailer +{ + u_short control; + u_short length; + u_int crc32; +}; + +struct cpcs_trailer_desc +{ + struct cpcs_trailer *cpcs; + dma_addr_t dma_addr; +}; + +struct ia_vcc +{ + int rxing; + int txing; + int NumCbrEntry; + u32 pcr; + u32 saved_tx_quota; + int flow_inc; + struct sk_buff_head txing_skb; + int ltimeout; + u8 vc_desc_cnt; + +}; + +struct abr_vc_table +{ + u_char status; + u_char rdf; + u_short air; + u_int res[3]; + u_int req_rm_cell_data1; + u_int req_rm_cell_data2; + u_int add_rm_cell_data1; + u_int add_rm_cell_data2; +}; + +/* 32 byte entries */ +struct main_vc +{ + u_short type; +#define ABR 0x8000 +#define UBR 0xc000 +#define CBR 0x0000 + /* ABR fields */ + u_short nrm; + u_short trm; + u_short rm_timestamp_hi; + u_short rm_timestamp_lo:8, + crm:8; + u_short remainder; /* ABR and UBR fields - last 10 bits*/ + u_short next_vc_sched; + u_short present_desc; /* all classes */ + u_short last_cell_slot; /* ABR and UBR */ + u_short pcr; + u_short fraction; + u_short icr; + u_short atdf; + u_short mcr; + u_short acr; + u_short unack:8, + status:8; /* all classes */ +#define UIOLI 0x80 +#define CRC_APPEND 0x40 /* for status field - CRC-32 append */ +#define ABR_STATE 0x02 + +}; + + +/* 8 byte entries */ +struct ext_vc +{ + u_short atm_hdr1; + u_short atm_hdr2; + u_short last_desc; + u_short out_of_rate_link; /* reserved for UBR and CBR */ +}; + + +#define DLE_ENTRIES 256 +#define DMA_INT_ENABLE 0x0002 /* use for both Tx and Rx */ +#define TX_DLE_PSI 0x0001 +#define DLE_TOTAL_SIZE (sizeof(struct dle)*DLE_ENTRIES) + +/* Descriptor List Entries (DLE) */ +struct dle +{ + u32 sys_pkt_addr; + u32 local_pkt_addr; + u32 bytes; + u16 prq_wr_ptr_data; + u16 mode; +}; + +struct dle_q +{ + struct dle *start; + struct dle *end; + struct dle *read; + struct dle *write; +}; + +struct free_desc_q +{ + int desc; /* Descriptor number */ + struct free_desc_q *next; +}; + +struct tx_buf_desc { + unsigned short desc_mode; + unsigned short vc_index; + unsigned short res1; /* reserved field */ + unsigned short bytes; + unsigned short buf_start_hi; + unsigned short buf_start_lo; + unsigned short res2[10]; /* reserved field */ +}; + + +struct rx_buf_desc { + unsigned short desc_mode; + unsigned short vc_index; + unsigned short vpi; + unsigned short bytes; + unsigned short buf_start_hi; + unsigned short buf_start_lo; + unsigned short dma_start_hi; + unsigned short dma_start_lo; + unsigned short crc_upper; + unsigned short crc_lower; + unsigned short res:8, timeout:8; + unsigned short res2[5]; /* reserved field */ +}; + +/*--------SAR stuff ---------------------*/ + +#define EPROM_SIZE 0x40000 /* says 64K in the docs ??? */ +#define MAC1_LEN 4 +#define MAC2_LEN 2 + +/*------------ PCI Memory Space Map, 128K SAR memory ----------------*/ +#define IPHASE5575_PCI_CONFIG_REG_BASE 0x0000 +#define IPHASE5575_BUS_CONTROL_REG_BASE 0x1000 /* offsets 0x00 - 0x3c */ +#define IPHASE5575_FRAG_CONTROL_REG_BASE 0x2000 +#define IPHASE5575_REASS_CONTROL_REG_BASE 0x3000 +#define IPHASE5575_DMA_CONTROL_REG_BASE 0x4000 +#define IPHASE5575_FRONT_END_REG_BASE IPHASE5575_DMA_CONTROL_REG_BASE +#define IPHASE5575_FRAG_CONTROL_RAM_BASE 0x10000 +#define IPHASE5575_REASS_CONTROL_RAM_BASE 0x20000 + +/*------------ Bus interface control registers -----------------*/ +#define IPHASE5575_BUS_CONTROL_REG 0x00 +#define IPHASE5575_BUS_STATUS_REG 0x01 /* actual offset 0x04 */ +#define IPHASE5575_MAC1 0x02 +#define IPHASE5575_REV 0x03 +#define IPHASE5575_MAC2 0x03 /*actual offset 0x0e-reg 0x0c*/ +#define IPHASE5575_EXT_RESET 0x04 +#define IPHASE5575_INT_RESET 0x05 /* addr 1c ?? reg 0x06 */ +#define IPHASE5575_PCI_ADDR_PAGE 0x07 /* reg 0x08, 0x09 ?? */ +#define IPHASE5575_EEPROM_ACCESS 0x0a /* actual offset 0x28 */ +#define IPHASE5575_CELL_FIFO_QUEUE_SZ 0x0b +#define IPHASE5575_CELL_FIFO_MARK_STATE 0x0c +#define IPHASE5575_CELL_FIFO_READ_PTR 0x0d +#define IPHASE5575_CELL_FIFO_WRITE_PTR 0x0e +#define IPHASE5575_CELL_FIFO_CELLS_AVL 0x0f /* actual offset 0x3c */ + +/* Bus Interface Control Register bits */ +#define CTRL_FE_RST 0x80000000 +#define CTRL_LED 0x40000000 +#define CTRL_25MBPHY 0x10000000 +#define CTRL_ENCMBMEM 0x08000000 +#define CTRL_ENOFFSEG 0x01000000 +#define CTRL_ERRMASK 0x00400000 +#define CTRL_DLETMASK 0x00100000 +#define CTRL_DLERMASK 0x00080000 +#define CTRL_FEMASK 0x00040000 +#define CTRL_SEGMASK 0x00020000 +#define CTRL_REASSMASK 0x00010000 +#define CTRL_CSPREEMPT 0x00002000 +#define CTRL_B128 0x00000200 +#define CTRL_B64 0x00000100 +#define CTRL_B48 0x00000080 +#define CTRL_B32 0x00000040 +#define CTRL_B16 0x00000020 +#define CTRL_B8 0x00000010 + +/* Bus Interface Status Register bits */ +#define STAT_CMEMSIZ 0xc0000000 +#define STAT_ADPARCK 0x20000000 +#define STAT_RESVD 0x1fffff80 +#define STAT_ERRINT 0x00000040 +#define STAT_MARKINT 0x00000020 +#define STAT_DLETINT 0x00000010 +#define STAT_DLERINT 0x00000008 +#define STAT_FEINT 0x00000004 +#define STAT_SEGINT 0x00000002 +#define STAT_REASSINT 0x00000001 + + +/*--------------- Segmentation control registers -----------------*/ +/* The segmentation registers are 16 bits access and the addresses + are defined as such so the addresses are the actual "offsets" */ +#define IDLEHEADHI 0x00 +#define IDLEHEADLO 0x01 +#define MAXRATE 0x02 +/* Values for MAXRATE register for 155Mbps and 25.6 Mbps operation */ +#define RATE155 0x64b1 // 16 bits float format +#define MAX_ATM_155 352768 // Cells/second p.118 +#define RATE25 0x5f9d + +#define STPARMS 0x03 +#define STPARMS_1K 0x008c +#define STPARMS_2K 0x0049 +#define STPARMS_4K 0x0026 +#define COMP_EN 0x4000 +#define CBR_EN 0x2000 +#define ABR_EN 0x0800 +#define UBR_EN 0x0400 + +#define ABRUBR_ARB 0x04 +#define RM_TYPE 0x05 +/*Value for RM_TYPE register for ATM Forum Traffic Mangement4.0 support*/ +#define RM_TYPE_4_0 0x0100 + +#define SEG_COMMAND_REG 0x17 +/* Values for the command register */ +#define RESET_SEG 0x0055 +#define RESET_SEG_STATE 0x00aa +#define RESET_TX_CELL_CTR 0x00cc + +#define CBR_PTR_BASE 0x20 +#define ABR_SBPTR_BASE 0x22 +#define UBR_SBPTR_BASE 0x23 +#define ABRWQ_BASE 0x26 +#define UBRWQ_BASE 0x27 +#define VCT_BASE 0x28 +#define VCTE_BASE 0x29 +#define CBR_TAB_BEG 0x2c +#define CBR_TAB_END 0x2d +#define PRQ_ST_ADR 0x30 +#define PRQ_ED_ADR 0x31 +#define PRQ_RD_PTR 0x32 +#define PRQ_WR_PTR 0x33 +#define TCQ_ST_ADR 0x34 +#define TCQ_ED_ADR 0x35 +#define TCQ_RD_PTR 0x36 +#define TCQ_WR_PTR 0x37 +#define SEG_QUEUE_BASE 0x40 +#define SEG_DESC_BASE 0x41 +#define MODE_REG_0 0x45 +#define T_ONLINE 0x0002 /* (i)chipSAR is online */ + +#define MODE_REG_1 0x46 +#define MODE_REG_1_VAL 0x0400 /*for propoer device operation*/ + +#define SEG_INTR_STATUS_REG 0x47 +#define SEG_MASK_REG 0x48 +#define TRANSMIT_DONE 0x0200 +#define TCQ_NOT_EMPTY 0x1000 /* this can be used for both the interrupt + status registers as well as the mask register */ + +#define CELL_CTR_HIGH_AUTO 0x49 +#define CELL_CTR_HIGH_NOAUTO 0xc9 +#define CELL_CTR_LO_AUTO 0x4a +#define CELL_CTR_LO_NOAUTO 0xca + +/* Diagnostic registers */ +#define NEXTDESC 0x59 +#define NEXTVC 0x5a +#define PSLOTCNT 0x5d +#define NEWDN 0x6a +#define NEWVC 0x6b +#define SBPTR 0x6c +#define ABRWQ_WRPTR 0x6f +#define ABRWQ_RDPTR 0x70 +#define UBRWQ_WRPTR 0x71 +#define UBRWQ_RDPTR 0x72 +#define CBR_VC 0x73 +#define ABR_SBVC 0x75 +#define UBR_SBVC 0x76 +#define ABRNEXTLINK 0x78 +#define UBRNEXTLINK 0x79 + + +/*----------------- Reassembly control registers ---------------------*/ +/* The reassembly registers are 16 bits access and the addresses + are defined as such so the addresses are the actual "offsets" */ +#define MODE_REG 0x00 +#define R_ONLINE 0x0002 /* (i)chip is online */ +#define IGN_RAW_FL 0x0004 + +#define PROTOCOL_ID 0x01 +#define REASS_MASK_REG 0x02 +#define REASS_INTR_STATUS_REG 0x03 +/* Interrupt Status register bits */ +#define RX_PKT_CTR_OF 0x8000 +#define RX_ERR_CTR_OF 0x4000 +#define RX_CELL_CTR_OF 0x1000 +#define RX_FREEQ_EMPT 0x0200 +#define RX_EXCPQ_FL 0x0080 +#define RX_RAWQ_FL 0x0010 +#define RX_EXCP_RCVD 0x0008 +#define RX_PKT_RCVD 0x0004 +#define RX_RAW_RCVD 0x0001 + +#define DRP_PKT_CNTR 0x04 +#define ERR_CNTR 0x05 +#define RAW_BASE_ADR 0x08 +#define CELL_CTR0 0x0c +#define CELL_CTR1 0x0d +#define REASS_COMMAND_REG 0x0f +/* Values for command register */ +#define RESET_REASS 0x0055 +#define RESET_REASS_STATE 0x00aa +#define RESET_DRP_PKT_CNTR 0x00f1 +#define RESET_ERR_CNTR 0x00f2 +#define RESET_CELL_CNTR 0x00f8 +#define RESET_REASS_ALL_REGS 0x00ff + +#define REASS_DESC_BASE 0x10 +#define VC_LKUP_BASE 0x11 +#define REASS_TABLE_BASE 0x12 +#define REASS_QUEUE_BASE 0x13 +#define PKT_TM_CNT 0x16 +#define TMOUT_RANGE 0x17 +#define INTRVL_CNTR 0x18 +#define TMOUT_INDX 0x19 +#define VP_LKUP_BASE 0x1c +#define VP_FILTER 0x1d +#define ABR_LKUP_BASE 0x1e +#define FREEQ_ST_ADR 0x24 +#define FREEQ_ED_ADR 0x25 +#define FREEQ_RD_PTR 0x26 +#define FREEQ_WR_PTR 0x27 +#define PCQ_ST_ADR 0x28 +#define PCQ_ED_ADR 0x29 +#define PCQ_RD_PTR 0x2a +#define PCQ_WR_PTR 0x2b +#define EXCP_Q_ST_ADR 0x2c +#define EXCP_Q_ED_ADR 0x2d +#define EXCP_Q_RD_PTR 0x2e +#define EXCP_Q_WR_PTR 0x2f +#define CC_FIFO_ST_ADR 0x34 +#define CC_FIFO_ED_ADR 0x35 +#define CC_FIFO_RD_PTR 0x36 +#define CC_FIFO_WR_PTR 0x37 +#define STATE_REG 0x38 +#define BUF_SIZE 0x42 +#define XTRA_RM_OFFSET 0x44 +#define DRP_PKT_CNTR_NC 0x84 +#define ERR_CNTR_NC 0x85 +#define CELL_CNTR0_NC 0x8c +#define CELL_CNTR1_NC 0x8d + +/* State Register bits */ +#define EXCPQ_EMPTY 0x0040 +#define PCQ_EMPTY 0x0010 +#define FREEQ_EMPTY 0x0004 + + +/*----------------- Front End registers/ DMA control --------------*/ +/* There is a lot of documentation error regarding these offsets ??? + eg:- 2 offsets given 800, a00 for rx counter + similarly many others + Remember again that the offsets are to be 4*register number, so + correct the #defines here +*/ +#define IPHASE5575_TX_COUNTER 0x200 /* offset - 0x800 */ +#define IPHASE5575_RX_COUNTER 0x280 /* offset - 0xa00 */ +#define IPHASE5575_TX_LIST_ADDR 0x300 /* offset - 0xc00 */ +#define IPHASE5575_RX_LIST_ADDR 0x380 /* offset - 0xe00 */ + +/*--------------------------- RAM ---------------------------*/ +/* These memory maps are actually offsets from the segmentation and reassembly RAM base addresses */ + +/* Segmentation Control Memory map */ +#define TX_DESC_BASE 0x0000 /* Buffer Decriptor Table */ +#define TX_COMP_Q 0x1000 /* Transmit Complete Queue */ +#define PKT_RDY_Q 0x1400 /* Packet Ready Queue */ +#define CBR_SCHED_TABLE 0x1800 /* CBR Table */ +#define UBR_SCHED_TABLE 0x3000 /* UBR Table */ +#define UBR_WAIT_Q 0x4000 /* UBR Wait Queue */ +#define ABR_SCHED_TABLE 0x5000 /* ABR Table */ +#define ABR_WAIT_Q 0x5800 /* ABR Wait Queue */ +#define EXT_VC_TABLE 0x6000 /* Extended VC Table */ +#define MAIN_VC_TABLE 0x8000 /* Main VC Table */ +#define SCHEDSZ 1024 /* ABR and UBR Scheduling Table size */ +#define TX_DESC_TABLE_SZ 128 /* Number of entries in the Transmit + Buffer Descriptor Table */ + +/* These are used as table offsets in Descriptor Table address generation */ +#define DESC_MODE 0x0 +#define VC_INDEX 0x1 +#define BYTE_CNT 0x3 +#define PKT_START_HI 0x4 +#define PKT_START_LO 0x5 + +/* Descriptor Mode Word Bits */ +#define EOM_EN 0x0800 +#define AAL5 0x0100 +#define APP_CRC32 0x0400 +#define CMPL_INT 0x1000 + +#define TABLE_ADDRESS(db, dn, to) \ + (((unsigned long)(db & 0x04)) << 16) | (dn << 5) | (to << 1) + +/* Reassembly Control Memory Map */ +#define RX_DESC_BASE 0x0000 /* Buffer Descriptor Table */ +#define VP_TABLE 0x5c00 /* VP Table */ +#define EXCEPTION_Q 0x5e00 /* Exception Queue */ +#define FREE_BUF_DESC_Q 0x6000 /* Free Buffer Descriptor Queue */ +#define PKT_COMP_Q 0x6800 /* Packet Complete Queue */ +#define REASS_TABLE 0x7000 /* Reassembly Table */ +#define RX_VC_TABLE 0x7800 /* VC Table */ +#define ABR_VC_TABLE 0x8000 /* ABR VC Table */ +#define RX_DESC_TABLE_SZ 736 /* Number of entries in the Receive + Buffer Descriptor Table */ +#define VP_TABLE_SZ 256 /* Number of entries in VPTable */ +#define RX_VC_TABLE_SZ 1024 /* Number of entries in VC Table */ +#define REASS_TABLE_SZ 1024 /* Number of entries in Reassembly Table */ + /* Buffer Descriptor Table */ +#define RX_ACT 0x8000 +#define RX_VPVC 0x4000 +#define RX_CNG 0x0040 +#define RX_CER 0x0008 +#define RX_PTE 0x0004 +#define RX_OFL 0x0002 +#define NUM_RX_EXCP 32 + +/* Reassembly Table */ +#define NO_AAL5_PKT 0x0000 +#define AAL5_PKT_REASSEMBLED 0x4000 +#define AAL5_PKT_TERMINATED 0x8000 +#define RAW_PKT 0xc000 +#define REASS_ABR 0x2000 + +/*-------------------- Base Registers --------------------*/ +#define REG_BASE IPHASE5575_BUS_CONTROL_REG_BASE +#define RAM_BASE IPHASE5575_FRAG_CONTROL_RAM_BASE +#define PHY_BASE IPHASE5575_FRONT_END_REG_BASE +#define SEG_BASE IPHASE5575_FRAG_CONTROL_REG_BASE +#define REASS_BASE IPHASE5575_REASS_CONTROL_REG_BASE + +typedef volatile u_int ffreg_t; +typedef u_int rreg_t; + +typedef struct _ffredn_t { + ffreg_t idlehead_high; /* Idle cell header (high) */ + ffreg_t idlehead_low; /* Idle cell header (low) */ + ffreg_t maxrate; /* Maximum rate */ + ffreg_t stparms; /* Traffic Management Parameters */ + ffreg_t abrubr_abr; /* ABRUBR Priority Byte 1, TCR Byte 0 */ + ffreg_t rm_type; /* */ + u_int filler5[0x17 - 0x06]; + ffreg_t cmd_reg; /* Command register */ + u_int filler18[0x20 - 0x18]; + ffreg_t cbr_base; /* CBR Pointer Base */ + ffreg_t vbr_base; /* VBR Pointer Base */ + ffreg_t abr_base; /* ABR Pointer Base */ + ffreg_t ubr_base; /* UBR Pointer Base */ + u_int filler24; + ffreg_t vbrwq_base; /* VBR Wait Queue Base */ + ffreg_t abrwq_base; /* ABR Wait Queue Base */ + ffreg_t ubrwq_base; /* UBR Wait Queue Base */ + ffreg_t vct_base; /* Main VC Table Base */ + ffreg_t vcte_base; /* Extended Main VC Table Base */ + u_int filler2a[0x2C - 0x2A]; + ffreg_t cbr_tab_beg; /* CBR Table Begin */ + ffreg_t cbr_tab_end; /* CBR Table End */ + ffreg_t cbr_pointer; /* CBR Pointer */ + u_int filler2f[0x30 - 0x2F]; + ffreg_t prq_st_adr; /* Packet Ready Queue Start Address */ + ffreg_t prq_ed_adr; /* Packet Ready Queue End Address */ + ffreg_t prq_rd_ptr; /* Packet Ready Queue read pointer */ + ffreg_t prq_wr_ptr; /* Packet Ready Queue write pointer */ + ffreg_t tcq_st_adr; /* Transmit Complete Queue Start Address*/ + ffreg_t tcq_ed_adr; /* Transmit Complete Queue End Address */ + ffreg_t tcq_rd_ptr; /* Transmit Complete Queue read pointer */ + ffreg_t tcq_wr_ptr; /* Transmit Complete Queue write pointer*/ + u_int filler38[0x40 - 0x38]; + ffreg_t queue_base; /* Base address for PRQ and TCQ */ + ffreg_t desc_base; /* Base address of descriptor table */ + u_int filler42[0x45 - 0x42]; + ffreg_t mode_reg_0; /* Mode register 0 */ + ffreg_t mode_reg_1; /* Mode register 1 */ + ffreg_t intr_status_reg;/* Interrupt Status register */ + ffreg_t mask_reg; /* Mask Register */ + ffreg_t cell_ctr_high1; /* Total cell transfer count (high) */ + ffreg_t cell_ctr_lo1; /* Total cell transfer count (low) */ + ffreg_t state_reg; /* Status register */ + u_int filler4c[0x58 - 0x4c]; + ffreg_t curr_desc_num; /* Contains the current descriptor num */ + ffreg_t next_desc; /* Next descriptor */ + ffreg_t next_vc; /* Next VC */ + u_int filler5b[0x5d - 0x5b]; + ffreg_t present_slot_cnt;/* Present slot count */ + u_int filler5e[0x6a - 0x5e]; + ffreg_t new_desc_num; /* New descriptor number */ + ffreg_t new_vc; /* New VC */ + ffreg_t sched_tbl_ptr; /* Schedule table pointer */ + ffreg_t vbrwq_wptr; /* VBR wait queue write pointer */ + ffreg_t vbrwq_rptr; /* VBR wait queue read pointer */ + ffreg_t abrwq_wptr; /* ABR wait queue write pointer */ + ffreg_t abrwq_rptr; /* ABR wait queue read pointer */ + ffreg_t ubrwq_wptr; /* UBR wait queue write pointer */ + ffreg_t ubrwq_rptr; /* UBR wait queue read pointer */ + ffreg_t cbr_vc; /* CBR VC */ + ffreg_t vbr_sb_vc; /* VBR SB VC */ + ffreg_t abr_sb_vc; /* ABR SB VC */ + ffreg_t ubr_sb_vc; /* UBR SB VC */ + ffreg_t vbr_next_link; /* VBR next link */ + ffreg_t abr_next_link; /* ABR next link */ + ffreg_t ubr_next_link; /* UBR next link */ + u_int filler7a[0x7c-0x7a]; + ffreg_t out_rate_head; /* Out of rate head */ + u_int filler7d[0xca-0x7d]; /* pad out to full address space */ + ffreg_t cell_ctr_high1_nc;/* Total cell transfer count (high) */ + ffreg_t cell_ctr_lo1_nc;/* Total cell transfer count (low) */ + u_int fillercc[0x100-0xcc]; /* pad out to full address space */ +} ffredn_t; + +typedef struct _rfredn_t { + rreg_t mode_reg_0; /* Mode register 0 */ + rreg_t protocol_id; /* Protocol ID */ + rreg_t mask_reg; /* Mask Register */ + rreg_t intr_status_reg;/* Interrupt status register */ + rreg_t drp_pkt_cntr; /* Dropped packet cntr (clear on read) */ + rreg_t err_cntr; /* Error Counter (cleared on read) */ + u_int filler6[0x08 - 0x06]; + rreg_t raw_base_adr; /* Base addr for raw cell Q */ + u_int filler2[0x0c - 0x09]; + rreg_t cell_ctr0; /* Cell Counter 0 (cleared when read) */ + rreg_t cell_ctr1; /* Cell Counter 1 (cleared when read) */ + u_int filler3[0x0f - 0x0e]; + rreg_t cmd_reg; /* Command register */ + rreg_t desc_base; /* Base address for description table */ + rreg_t vc_lkup_base; /* Base address for VC lookup table */ + rreg_t reass_base; /* Base address for reassembler table */ + rreg_t queue_base; /* Base address for Communication queue */ + u_int filler14[0x16 - 0x14]; + rreg_t pkt_tm_cnt; /* Packet Timeout and count register */ + rreg_t tmout_range; /* Range of reassembley IDs for timeout */ + rreg_t intrvl_cntr; /* Packet aging interval counter */ + rreg_t tmout_indx; /* index of pkt being tested for aging */ + u_int filler1a[0x1c - 0x1a]; + rreg_t vp_lkup_base; /* Base address for VP lookup table */ + rreg_t vp_filter; /* VP filter register */ + rreg_t abr_lkup_base; /* Base address of ABR VC Table */ + u_int filler1f[0x24 - 0x1f]; + rreg_t fdq_st_adr; /* Free desc queue start address */ + rreg_t fdq_ed_adr; /* Free desc queue end address */ + rreg_t fdq_rd_ptr; /* Free desc queue read pointer */ + rreg_t fdq_wr_ptr; /* Free desc queue write pointer */ + rreg_t pcq_st_adr; /* Packet Complete queue start address */ + rreg_t pcq_ed_adr; /* Packet Complete queue end address */ + rreg_t pcq_rd_ptr; /* Packet Complete queue read pointer */ + rreg_t pcq_wr_ptr; /* Packet Complete queue write pointer */ + rreg_t excp_st_adr; /* Exception queue start address */ + rreg_t excp_ed_adr; /* Exception queue end address */ + rreg_t excp_rd_ptr; /* Exception queue read pointer */ + rreg_t excp_wr_ptr; /* Exception queue write pointer */ + u_int filler30[0x34 - 0x30]; + rreg_t raw_st_adr; /* Raw Cell start address */ + rreg_t raw_ed_adr; /* Raw Cell end address */ + rreg_t raw_rd_ptr; /* Raw Cell read pointer */ + rreg_t raw_wr_ptr; /* Raw Cell write pointer */ + rreg_t state_reg; /* State Register */ + u_int filler39[0x42 - 0x39]; + rreg_t buf_size; /* Buffer size */ + u_int filler43; + rreg_t xtra_rm_offset; /* Offset of the additional turnaround RM */ + u_int filler45[0x84 - 0x45]; + rreg_t drp_pkt_cntr_nc;/* Dropped Packet cntr, Not clear on rd */ + rreg_t err_cntr_nc; /* Error Counter, Not clear on read */ + u_int filler86[0x8c - 0x86]; + rreg_t cell_ctr0_nc; /* Cell Counter 0, Not clear on read */ + rreg_t cell_ctr1_nc; /* Cell Counter 1, Not clear on read */ + u_int filler8e[0x100-0x8e]; /* pad out to full address space */ +} rfredn_t; + +typedef struct { + /* Atlantic */ + ffredn_t ffredn; /* F FRED */ + rfredn_t rfredn; /* R FRED */ +} ia_regs_t; + +typedef struct { + u_short f_vc_type; /* VC type */ + u_short f_nrm; /* Nrm */ + u_short f_nrmexp; /* Nrm Exp */ + u_short reserved6; /* */ + u_short f_crm; /* Crm */ + u_short reserved10; /* Reserved */ + u_short reserved12; /* Reserved */ + u_short reserved14; /* Reserved */ + u_short last_cell_slot; /* last_cell_slot_count */ + u_short f_pcr; /* Peak Cell Rate */ + u_short fraction; /* fraction */ + u_short f_icr; /* Initial Cell Rate */ + u_short f_cdf; /* */ + u_short f_mcr; /* Minimum Cell Rate */ + u_short f_acr; /* Allowed Cell Rate */ + u_short f_status; /* */ +} f_vc_abr_entry; + +typedef struct { + u_short r_status_rdf; /* status + RDF */ + u_short r_air; /* AIR */ + u_short reserved4[14]; /* Reserved */ +} r_vc_abr_entry; + +#define MRM 3 + +typedef struct srv_cls_param { + u32 class_type; /* CBR/VBR/ABR/UBR; use the enum above */ + u32 pcr; /* Peak Cell Rate (24-bit) */ + /* VBR parameters */ + u32 scr; /* sustainable cell rate */ + u32 max_burst_size; /* ?? cell rate or data rate */ + + /* ABR only UNI 4.0 Parameters */ + u32 mcr; /* Min Cell Rate (24-bit) */ + u32 icr; /* Initial Cell Rate (24-bit) */ + u32 tbe; /* Transient Buffer Exposure (24-bit) */ + u32 frtt; /* Fixed Round Trip Time (24-bit) */ + +#if 0 /* Additional Parameters of TM 4.0 */ +bits 31 30 29 28 27-25 24-22 21-19 18-9 +----------------------------------------------------------------------------- +| NRM present | TRM prsnt | CDF prsnt | ADTF prsnt | NRM | TRM | CDF | ADTF | +----------------------------------------------------------------------------- +#endif /* 0 */ + + u8 nrm; /* Max # of Cells for each forward RM + cell (3-bit) */ + u8 trm; /* Time between forward RM cells (3-bit) */ + u16 adtf; /* ACR Decrease Time Factor (10-bit) */ + u8 cdf; /* Cutoff Decrease Factor (3-bit) */ + u8 rif; /* Rate Increment Factor (4-bit) */ + u8 rdf; /* Rate Decrease Factor (4-bit) */ + u8 reserved; /* 8 bits to keep structure word aligned */ +} srv_cls_param_t; + +struct testTable_t { + u16 lastTime; + u16 fract; + u8 vc_status; +}; + +typedef struct { + u16 vci; + u16 error; +} RX_ERROR_Q; + +typedef struct { + u8 active: 1; + u8 abr: 1; + u8 ubr: 1; + u8 cnt: 5; +#define VC_ACTIVE 0x01 +#define VC_ABR 0x02 +#define VC_UBR 0x04 +} vcstatus_t; + +struct ia_rfL_t { + u32 fdq_st; /* Free desc queue start address */ + u32 fdq_ed; /* Free desc queue end address */ + u32 fdq_rd; /* Free desc queue read pointer */ + u32 fdq_wr; /* Free desc queue write pointer */ + u32 pcq_st; /* Packet Complete queue start address */ + u32 pcq_ed; /* Packet Complete queue end address */ + u32 pcq_rd; /* Packet Complete queue read pointer */ + u32 pcq_wr; /* Packet Complete queue write pointer */ +}; + +struct ia_ffL_t { + u32 prq_st; /* Packet Ready Queue Start Address */ + u32 prq_ed; /* Packet Ready Queue End Address */ + u32 prq_wr; /* Packet Ready Queue write pointer */ + u32 tcq_st; /* Transmit Complete Queue Start Address*/ + u32 tcq_ed; /* Transmit Complete Queue End Address */ + u32 tcq_rd; /* Transmit Complete Queue read pointer */ +}; + +struct desc_tbl_t { + u32 timestamp; + struct ia_vcc *iavcc; + struct sk_buff *txskb; +}; + +typedef struct ia_rtn_q { + struct desc_tbl_t data; + struct ia_rtn_q *next, *tail; +} IARTN_Q; + +#define SUNI_LOSV 0x04 +enum ia_suni { + SUNI_MASTER_RESET = 0x000, /* SUNI Master Reset and Identity */ + SUNI_MASTER_CONFIG = 0x004, /* SUNI Master Configuration */ + SUNI_MASTER_INTR_STAT = 0x008, /* SUNI Master Interrupt Status */ + SUNI_RESERVED1 = 0x00c, /* Reserved */ + SUNI_MASTER_CLK_MONITOR = 0x010, /* SUNI Master Clock Monitor */ + SUNI_MASTER_CONTROL = 0x014, /* SUNI Master Clock Monitor */ + /* Reserved (10) */ + SUNI_RSOP_CONTROL = 0x040, /* RSOP Control/Interrupt Enable */ + SUNI_RSOP_STATUS = 0x044, /* RSOP Status/Interrupt States */ + SUNI_RSOP_SECTION_BIP8L = 0x048, /* RSOP Section BIP-8 LSB */ + SUNI_RSOP_SECTION_BIP8M = 0x04c, /* RSOP Section BIP-8 MSB */ + + SUNI_TSOP_CONTROL = 0x050, /* TSOP Control */ + SUNI_TSOP_DIAG = 0x054, /* TSOP Disgnostics */ + /* Reserved (2) */ + SUNI_RLOP_CS = 0x060, /* RLOP Control/Status */ + SUNI_RLOP_INTR = 0x064, /* RLOP Interrupt Enable/Status */ + SUNI_RLOP_LINE_BIP24L = 0x068, /* RLOP Line BIP-24 LSB */ + SUNI_RLOP_LINE_BIP24 = 0x06c, /* RLOP Line BIP-24 */ + SUNI_RLOP_LINE_BIP24M = 0x070, /* RLOP Line BIP-24 MSB */ + SUNI_RLOP_LINE_FEBEL = 0x074, /* RLOP Line FEBE LSB */ + SUNI_RLOP_LINE_FEBE = 0x078, /* RLOP Line FEBE */ + SUNI_RLOP_LINE_FEBEM = 0x07c, /* RLOP Line FEBE MSB */ + + SUNI_TLOP_CONTROL = 0x080, /* TLOP Control */ + SUNI_TLOP_DISG = 0x084, /* TLOP Disgnostics */ + /* Reserved (14) */ + SUNI_RPOP_CS = 0x0c0, /* RPOP Status/Control */ + SUNI_RPOP_INTR = 0x0c4, /* RPOP Interrupt/Status */ + SUNI_RPOP_RESERVED = 0x0c8, /* RPOP Reserved */ + SUNI_RPOP_INTR_ENA = 0x0cc, /* RPOP Interrupt Enable */ + /* Reserved (3) */ + SUNI_RPOP_PATH_SIG = 0x0dc, /* RPOP Path Signal Label */ + SUNI_RPOP_BIP8L = 0x0e0, /* RPOP Path BIP-8 LSB */ + SUNI_RPOP_BIP8M = 0x0e4, /* RPOP Path BIP-8 MSB */ + SUNI_RPOP_FEBEL = 0x0e8, /* RPOP Path FEBE LSB */ + SUNI_RPOP_FEBEM = 0x0ec, /* RPOP Path FEBE MSB */ + /* Reserved (4) */ + SUNI_TPOP_CNTRL_DAIG = 0x100, /* TPOP Control/Disgnostics */ + SUNI_TPOP_POINTER_CTRL = 0x104, /* TPOP Pointer Control */ + SUNI_TPOP_SOURCER_CTRL = 0x108, /* TPOP Source Control */ + /* Reserved (2) */ + SUNI_TPOP_ARB_PRTL = 0x114, /* TPOP Arbitrary Pointer LSB */ + SUNI_TPOP_ARB_PRTM = 0x118, /* TPOP Arbitrary Pointer MSB */ + SUNI_TPOP_RESERVED2 = 0x11c, /* TPOP Reserved */ + SUNI_TPOP_PATH_SIG = 0x120, /* TPOP Path Signal Lable */ + SUNI_TPOP_PATH_STATUS = 0x124, /* TPOP Path Status */ + /* Reserved (6) */ + SUNI_RACP_CS = 0x140, /* RACP Control/Status */ + SUNI_RACP_INTR = 0x144, /* RACP Interrupt Enable/Status */ + SUNI_RACP_HDR_PATTERN = 0x148, /* RACP Match Header Pattern */ + SUNI_RACP_HDR_MASK = 0x14c, /* RACP Match Header Mask */ + SUNI_RACP_CORR_HCS = 0x150, /* RACP Correctable HCS Error Count */ + SUNI_RACP_UNCORR_HCS = 0x154, /* RACP Uncorrectable HCS Err Count */ + /* Reserved (10) */ + SUNI_TACP_CONTROL = 0x180, /* TACP Control */ + SUNI_TACP_IDLE_HDR_PAT = 0x184, /* TACP Idle Cell Header Pattern */ + SUNI_TACP_IDLE_PAY_PAY = 0x188, /* TACP Idle Cell Payld Octet Patrn */ + /* Reserved (5) */ + /* Reserved (24) */ + /* FIXME: unused but name conflicts. + * SUNI_MASTER_TEST = 0x200, SUNI Master Test */ + SUNI_RESERVED_TEST = 0x204 /* SUNI Reserved for Test */ +}; + +typedef struct _SUNI_STATS_ +{ + u32 valid; // 1 = oc3 PHY card + u32 carrier_detect; // GPIN input + // RSOP: receive section overhead processor + u16 rsop_oof_state; // 1 = out of frame + u16 rsop_lof_state; // 1 = loss of frame + u16 rsop_los_state; // 1 = loss of signal + u32 rsop_los_count; // loss of signal count + u32 rsop_bse_count; // section BIP-8 error count + // RLOP: receive line overhead processor + u16 rlop_ferf_state; // 1 = far end receive failure + u16 rlop_lais_state; // 1 = line AIS + u32 rlop_lbe_count; // BIP-24 count + u32 rlop_febe_count; // FEBE count; + // RPOP: receive path overhead processor + u16 rpop_lop_state; // 1 = LOP + u16 rpop_pais_state; // 1 = path AIS + u16 rpop_pyel_state; // 1 = path yellow alert + u32 rpop_bip_count; // path BIP-8 error count + u32 rpop_febe_count; // path FEBE error count + u16 rpop_psig; // path signal label value + // RACP: receive ATM cell processor + u16 racp_hp_state; // hunt/presync state + u32 racp_fu_count; // FIFO underrun count + u32 racp_fo_count; // FIFO overrun count + u32 racp_chcs_count; // correctable HCS error count + u32 racp_uchcs_count; // uncorrectable HCS error count +} IA_SUNI_STATS; + +typedef struct iadev_priv { + /*-----base pointers into (i)chipSAR+ address space */ + u32 __iomem *phy; /* Base pointer into phy (SUNI). */ + u32 __iomem *dma; /* Base pointer into DMA control registers. */ + u32 __iomem *reg; /* Base pointer to SAR registers. */ + u32 __iomem *seg_reg; /* base pointer to segmentation engine + internal registers */ + u32 __iomem *reass_reg; /* base pointer to reassemble engine + internal registers */ + u32 __iomem *ram; /* base pointer to SAR RAM */ + void __iomem *seg_ram; + void __iomem *reass_ram; + struct dle_q tx_dle_q; + struct free_desc_q *tx_free_desc_qhead; + struct sk_buff_head tx_dma_q, tx_backlog; + spinlock_t tx_lock; + IARTN_Q tx_return_q; + u32 close_pending; + wait_queue_head_t close_wait; + wait_queue_head_t timeout_wait; + struct cpcs_trailer_desc *tx_buf; + u16 num_tx_desc, tx_buf_sz, rate_limit; + u32 tx_cell_cnt, tx_pkt_cnt; + void __iomem *MAIN_VC_TABLE_ADDR, *EXT_VC_TABLE_ADDR, *ABR_SCHED_TABLE_ADDR; + struct dle_q rx_dle_q; + struct free_desc_q *rx_free_desc_qhead; + struct sk_buff_head rx_dma_q; + spinlock_t rx_lock; + struct atm_vcc **rx_open; /* list of all open VCs */ + u16 num_rx_desc, rx_buf_sz, rxing; + u32 rx_pkt_ram, rx_tmp_cnt; + unsigned long rx_tmp_jif; + void __iomem *RX_DESC_BASE_ADDR; + u32 drop_rxpkt, drop_rxcell, rx_cell_cnt, rx_pkt_cnt; + struct atm_dev *next_board; /* other iphase devices */ + struct pci_dev *pci; + int mem; + unsigned int real_base; /* real and virtual base address */ + void __iomem *base; + unsigned int pci_map_size; /*pci map size of board */ + unsigned char irq; + unsigned char bus; + unsigned char dev_fn; + u_short phy_type; + u_short num_vc, memSize, memType; + struct ia_ffL_t ffL; + struct ia_rfL_t rfL; + /* Suni stat */ + // IA_SUNI_STATS suni_stats; + unsigned char carrier_detect; + /* CBR related */ + // transmit DMA & Receive + unsigned int tx_dma_cnt; // number of elements on dma queue + unsigned int rx_dma_cnt; // number of elements on rx dma queue + unsigned int NumEnabledCBR; // number of CBR VCI's enabled. CBR + // receive MARK for Cell FIFO + unsigned int rx_mark_cnt; // number of elements on mark queue + unsigned int CbrTotEntries; // Total CBR Entries in Scheduling Table. + unsigned int CbrRemEntries; // Remaining CBR Entries in Scheduling Table. + unsigned int CbrEntryPt; // CBR Sched Table Entry Point. + unsigned int Granularity; // CBR Granularity given Table Size. + /* ABR related */ + unsigned int sum_mcr, sum_cbr, LineRate; + unsigned int n_abr; + struct desc_tbl_t *desc_tbl; + u_short host_tcq_wr; + struct testTable_t **testTable; + dma_addr_t tx_dle_dma; + dma_addr_t rx_dle_dma; +} IADEV; + + +#define INPH_IA_DEV(d) ((IADEV *) (d)->dev_data) +#define INPH_IA_VCC(v) ((struct ia_vcc *) (v)->dev_data) + +/******************* IDT77105 25MB/s PHY DEFINE *****************************/ +enum ia_mb25 { + MB25_MASTER_CTRL = 0x00, /* Master control */ + MB25_INTR_STATUS = 0x04, /* Interrupt status */ + MB25_DIAG_CONTROL = 0x08, /* Diagnostic control */ + MB25_LED_HEC = 0x0c, /* LED driver and HEC status/control */ + MB25_LOW_BYTE_COUNTER = 0x10, + MB25_HIGH_BYTE_COUNTER = 0x14 +}; + +/* + * Master Control + */ +#define MB25_MC_UPLO 0x80 /* UPLO */ +#define MB25_MC_DREC 0x40 /* Discard receive cell errors */ +#define MB25_MC_ECEIO 0x20 /* Enable Cell Error Interrupts Only */ +#define MB25_MC_TDPC 0x10 /* Transmit data parity check */ +#define MB25_MC_DRIC 0x08 /* Discard receive idle cells */ +#define MB25_MC_HALTTX 0x04 /* Halt Tx */ +#define MB25_MC_UMS 0x02 /* UTOPIA mode select */ +#define MB25_MC_ENABLED 0x01 /* Enable interrupt */ + +/* + * Interrupt Status + */ +#define MB25_IS_GSB 0x40 /* GOOD Symbol Bit */ +#define MB25_IS_HECECR 0x20 /* HEC error cell received */ +#define MB25_IS_SCR 0x10 /* "Short Cell" Received */ +#define MB25_IS_TPE 0x08 /* Trnamsit Parity Error */ +#define MB25_IS_RSCC 0x04 /* Receive Signal Condition change */ +#define MB25_IS_RCSE 0x02 /* Received Cell Symbol Error */ +#define MB25_IS_RFIFOO 0x01 /* Received FIFO Overrun */ + +/* + * Diagnostic Control + */ +#define MB25_DC_FTXCD 0x80 /* Force TxClav deassert */ +#define MB25_DC_RXCOS 0x40 /* RxClav operation select */ +#define MB25_DC_ECEIO 0x20 /* Single/Multi-PHY config select */ +#define MB25_DC_RLFLUSH 0x10 /* Clear receive FIFO */ +#define MB25_DC_IXPE 0x08 /* Insert xmit payload error */ +#define MB25_DC_IXHECE 0x04 /* Insert Xmit HEC Error */ +#define MB25_DC_LB_MASK 0x03 /* Loopback control mask */ + +#define MB25_DC_LL 0x03 /* Line Loopback */ +#define MB25_DC_PL 0x02 /* PHY Loopback */ +#define MB25_DC_NM 0x00 + +#define FE_MASK 0x00F0 +#define FE_MULTI_MODE 0x0000 +#define FE_SINGLE_MODE 0x0010 +#define FE_UTP_OPTION 0x0020 +#define FE_25MBIT_PHY 0x0040 +#define FE_DS3_PHY 0x0080 /* DS3 */ +#define FE_E3_PHY 0x0090 /* E3 */ + +/*********************** SUNI_PM7345 PHY DEFINE HERE *********************/ +enum suni_pm7345 { + SUNI_CONFIG = 0x000, /* SUNI Configuration */ + SUNI_INTR_ENBL = 0x004, /* SUNI Interrupt Enable */ + SUNI_INTR_STAT = 0x008, /* SUNI Interrupt Status */ + SUNI_CONTROL = 0x00c, /* SUNI Control */ + SUNI_ID_RESET = 0x010, /* SUNI Reset and Identity */ + SUNI_DATA_LINK_CTRL = 0x014, + SUNI_RBOC_CONF_INTR_ENBL = 0x018, + SUNI_RBOC_STAT = 0x01c, + SUNI_DS3_FRM_CFG = 0x020, + SUNI_DS3_FRM_INTR_ENBL = 0x024, + SUNI_DS3_FRM_INTR_STAT = 0x028, + SUNI_DS3_FRM_STAT = 0x02c, + SUNI_RFDL_CFG = 0x030, + SUNI_RFDL_ENBL_STAT = 0x034, + SUNI_RFDL_STAT = 0x038, + SUNI_RFDL_DATA = 0x03c, + SUNI_PMON_CHNG = 0x040, + SUNI_PMON_INTR_ENBL_STAT = 0x044, + /* SUNI_RESERVED1 (0x13 - 0x11) */ + SUNI_PMON_LCV_EVT_CNT_LSB = 0x050, + SUNI_PMON_LCV_EVT_CNT_MSB = 0x054, + SUNI_PMON_FBE_EVT_CNT_LSB = 0x058, + SUNI_PMON_FBE_EVT_CNT_MSB = 0x05c, + SUNI_PMON_SEZ_DET_CNT_LSB = 0x060, + SUNI_PMON_SEZ_DET_CNT_MSB = 0x064, + SUNI_PMON_PE_EVT_CNT_LSB = 0x068, + SUNI_PMON_PE_EVT_CNT_MSB = 0x06c, + SUNI_PMON_PPE_EVT_CNT_LSB = 0x070, + SUNI_PMON_PPE_EVT_CNT_MSB = 0x074, + SUNI_PMON_FEBE_EVT_CNT_LSB = 0x078, + SUNI_PMON_FEBE_EVT_CNT_MSB = 0x07c, + SUNI_DS3_TRAN_CFG = 0x080, + SUNI_DS3_TRAN_DIAG = 0x084, + /* SUNI_RESERVED2 (0x23 - 0x21) */ + SUNI_XFDL_CFG = 0x090, + SUNI_XFDL_INTR_ST = 0x094, + SUNI_XFDL_XMIT_DATA = 0x098, + SUNI_XBOC_CODE = 0x09c, + SUNI_SPLR_CFG = 0x0a0, + SUNI_SPLR_INTR_EN = 0x0a4, + SUNI_SPLR_INTR_ST = 0x0a8, + SUNI_SPLR_STATUS = 0x0ac, + SUNI_SPLT_CFG = 0x0b0, + SUNI_SPLT_CNTL = 0x0b4, + SUNI_SPLT_DIAG_G1 = 0x0b8, + SUNI_SPLT_F1 = 0x0bc, + SUNI_CPPM_LOC_METERS = 0x0c0, + SUNI_CPPM_CHG_OF_CPPM_PERF_METR = 0x0c4, + SUNI_CPPM_B1_ERR_CNT_LSB = 0x0c8, + SUNI_CPPM_B1_ERR_CNT_MSB = 0x0cc, + SUNI_CPPM_FRAMING_ERR_CNT_LSB = 0x0d0, + SUNI_CPPM_FRAMING_ERR_CNT_MSB = 0x0d4, + SUNI_CPPM_FEBE_CNT_LSB = 0x0d8, + SUNI_CPPM_FEBE_CNT_MSB = 0x0dc, + SUNI_CPPM_HCS_ERR_CNT_LSB = 0x0e0, + SUNI_CPPM_HCS_ERR_CNT_MSB = 0x0e4, + SUNI_CPPM_IDLE_UN_CELL_CNT_LSB = 0x0e8, + SUNI_CPPM_IDLE_UN_CELL_CNT_MSB = 0x0ec, + SUNI_CPPM_RCV_CELL_CNT_LSB = 0x0f0, + SUNI_CPPM_RCV_CELL_CNT_MSB = 0x0f4, + SUNI_CPPM_XMIT_CELL_CNT_LSB = 0x0f8, + SUNI_CPPM_XMIT_CELL_CNT_MSB = 0x0fc, + SUNI_RXCP_CTRL = 0x100, + SUNI_RXCP_FCTRL = 0x104, + SUNI_RXCP_INTR_EN_STS = 0x108, + SUNI_RXCP_IDLE_PAT_H1 = 0x10c, + SUNI_RXCP_IDLE_PAT_H2 = 0x110, + SUNI_RXCP_IDLE_PAT_H3 = 0x114, + SUNI_RXCP_IDLE_PAT_H4 = 0x118, + SUNI_RXCP_IDLE_MASK_H1 = 0x11c, + SUNI_RXCP_IDLE_MASK_H2 = 0x120, + SUNI_RXCP_IDLE_MASK_H3 = 0x124, + SUNI_RXCP_IDLE_MASK_H4 = 0x128, + SUNI_RXCP_CELL_PAT_H1 = 0x12c, + SUNI_RXCP_CELL_PAT_H2 = 0x130, + SUNI_RXCP_CELL_PAT_H3 = 0x134, + SUNI_RXCP_CELL_PAT_H4 = 0x138, + SUNI_RXCP_CELL_MASK_H1 = 0x13c, + SUNI_RXCP_CELL_MASK_H2 = 0x140, + SUNI_RXCP_CELL_MASK_H3 = 0x144, + SUNI_RXCP_CELL_MASK_H4 = 0x148, + SUNI_RXCP_HCS_CS = 0x14c, + SUNI_RXCP_LCD_CNT_THRESHOLD = 0x150, + /* SUNI_RESERVED3 (0x57 - 0x54) */ + SUNI_TXCP_CTRL = 0x160, + SUNI_TXCP_INTR_EN_STS = 0x164, + SUNI_TXCP_IDLE_PAT_H1 = 0x168, + SUNI_TXCP_IDLE_PAT_H2 = 0x16c, + SUNI_TXCP_IDLE_PAT_H3 = 0x170, + SUNI_TXCP_IDLE_PAT_H4 = 0x174, + SUNI_TXCP_IDLE_PAT_H5 = 0x178, + SUNI_TXCP_IDLE_PAYLOAD = 0x17c, + SUNI_E3_FRM_FRAM_OPTIONS = 0x180, + SUNI_E3_FRM_MAINT_OPTIONS = 0x184, + SUNI_E3_FRM_FRAM_INTR_ENBL = 0x188, + SUNI_E3_FRM_FRAM_INTR_IND_STAT = 0x18c, + SUNI_E3_FRM_MAINT_INTR_ENBL = 0x190, + SUNI_E3_FRM_MAINT_INTR_IND = 0x194, + SUNI_E3_FRM_MAINT_STAT = 0x198, + SUNI_RESERVED4 = 0x19c, + SUNI_E3_TRAN_FRAM_OPTIONS = 0x1a0, + SUNI_E3_TRAN_STAT_DIAG_OPTIONS = 0x1a4, + SUNI_E3_TRAN_BIP_8_ERR_MASK = 0x1a8, + SUNI_E3_TRAN_MAINT_ADAPT_OPTS = 0x1ac, + SUNI_TTB_CTRL = 0x1b0, + SUNI_TTB_TRAIL_TRACE_ID_STAT = 0x1b4, + SUNI_TTB_IND_ADDR = 0x1b8, + SUNI_TTB_IND_DATA = 0x1bc, + SUNI_TTB_EXP_PAYLOAD_TYPE = 0x1c0, + SUNI_TTB_PAYLOAD_TYPE_CTRL_STAT = 0x1c4, + /* SUNI_PAD5 (0x7f - 0x71) */ + SUNI_MASTER_TEST = 0x200, + /* SUNI_PAD6 (0xff - 0x80) */ +}; + +#define SUNI_PM7345_T suni_pm7345_t +#define SUNI_PM7345 0x20 /* Suni chip type */ +#define SUNI_PM5346 0x30 /* Suni chip type */ +/* + * SUNI_PM7345 Configuration + */ +#define SUNI_PM7345_CLB 0x01 /* Cell loopback */ +#define SUNI_PM7345_PLB 0x02 /* Payload loopback */ +#define SUNI_PM7345_DLB 0x04 /* Diagnostic loopback */ +#define SUNI_PM7345_LLB 0x80 /* Line loopback */ +#define SUNI_PM7345_E3ENBL 0x40 /* E3 enable bit */ +#define SUNI_PM7345_LOOPT 0x10 /* LOOPT enable bit */ +#define SUNI_PM7345_FIFOBP 0x20 /* FIFO bypass */ +#define SUNI_PM7345_FRMRBP 0x08 /* Framer bypass */ +/* + * DS3 FRMR Interrupt Enable + */ +#define SUNI_DS3_COFAE 0x80 /* Enable change of frame align */ +#define SUNI_DS3_REDE 0x40 /* Enable DS3 RED state intr */ +#define SUNI_DS3_CBITE 0x20 /* Enable Appl ID channel intr */ +#define SUNI_DS3_FERFE 0x10 /* Enable Far End Receive Failure intr*/ +#define SUNI_DS3_IDLE 0x08 /* Enable Idle signal intr */ +#define SUNI_DS3_AISE 0x04 /* Enable Alarm Indication signal intr*/ +#define SUNI_DS3_OOFE 0x02 /* Enable Out of frame intr */ +#define SUNI_DS3_LOSE 0x01 /* Enable Loss of signal intr */ + +/* + * DS3 FRMR Status + */ +#define SUNI_DS3_ACE 0x80 /* Additional Configuration Reg */ +#define SUNI_DS3_REDV 0x40 /* DS3 RED state */ +#define SUNI_DS3_CBITV 0x20 /* Application ID channel state */ +#define SUNI_DS3_FERFV 0x10 /* Far End Receive Failure state*/ +#define SUNI_DS3_IDLV 0x08 /* Idle signal state */ +#define SUNI_DS3_AISV 0x04 /* Alarm Indication signal state*/ +#define SUNI_DS3_OOFV 0x02 /* Out of frame state */ +#define SUNI_DS3_LOSV 0x01 /* Loss of signal state */ + +/* + * E3 FRMR Interrupt/Status + */ +#define SUNI_E3_CZDI 0x40 /* Consecutive Zeros indicator */ +#define SUNI_E3_LOSI 0x20 /* Loss of signal intr status */ +#define SUNI_E3_LCVI 0x10 /* Line code violation intr */ +#define SUNI_E3_COFAI 0x08 /* Change of frame align intr */ +#define SUNI_E3_OOFI 0x04 /* Out of frame intr status */ +#define SUNI_E3_LOS 0x02 /* Loss of signal state */ +#define SUNI_E3_OOF 0x01 /* Out of frame state */ + +/* + * E3 FRMR Maintenance Status + */ +#define SUNI_E3_AISD 0x80 /* Alarm Indication signal state*/ +#define SUNI_E3_FERF_RAI 0x40 /* FERF/RAI indicator */ +#define SUNI_E3_FEBE 0x20 /* Far End Block Error indicator*/ + +/* + * RXCP Control/Status + */ +#define SUNI_DS3_HCSPASS 0x80 /* Pass cell with HEC errors */ +#define SUNI_DS3_HCSDQDB 0x40 /* Control octets in HCS calc */ +#define SUNI_DS3_HCSADD 0x20 /* Add coset poly */ +#define SUNI_DS3_HCK 0x10 /* Control FIFO data path integ chk*/ +#define SUNI_DS3_BLOCK 0x08 /* Enable cell filtering */ +#define SUNI_DS3_DSCR 0x04 /* Disable payload descrambling */ +#define SUNI_DS3_OOCDV 0x02 /* Cell delineation state */ +#define SUNI_DS3_FIFORST 0x01 /* Cell FIFO reset */ + +/* + * RXCP Interrupt Enable/Status + */ +#define SUNI_DS3_OOCDE 0x80 /* Intr enable, change in CDS */ +#define SUNI_DS3_HCSE 0x40 /* Intr enable, corr HCS errors */ +#define SUNI_DS3_FIFOE 0x20 /* Intr enable, unco HCS errors */ +#define SUNI_DS3_OOCDI 0x10 /* SYNC state */ +#define SUNI_DS3_UHCSI 0x08 /* Uncorr. HCS errors detected */ +#define SUNI_DS3_COCAI 0x04 /* Corr. HCS errors detected */ +#define SUNI_DS3_FOVRI 0x02 /* FIFO overrun */ +#define SUNI_DS3_FUDRI 0x01 /* FIFO underrun */ + +///////////////////SUNI_PM7345 PHY DEFINE END ///////////////////////////// + +/* ia_eeprom define*/ +#define MEM_SIZE_MASK 0x000F /* mask of 4 bits defining memory size*/ +#define MEM_SIZE_128K 0x0000 /* board has 128k buffer */ +#define MEM_SIZE_512K 0x0001 /* board has 512K of buffer */ +#define MEM_SIZE_1M 0x0002 /* board has 1M of buffer */ + /* 0x3 to 0xF are reserved for future */ + +#define FE_MASK 0x00F0 /* mask of 4 bits defining FE type */ +#define FE_MULTI_MODE 0x0000 /* 155 MBit multimode fiber */ +#define FE_SINGLE_MODE 0x0010 /* 155 MBit single mode laser */ +#define FE_UTP_OPTION 0x0020 /* 155 MBit UTP front end */ + +#define NOVRAM_SIZE 64 +#define CMD_LEN 10 + +/*********** + * + * Switches and defines for header files. + * + * The following defines are used to turn on and off + * various options in the header files. Primarily useful + * for debugging. + * + ***********/ + +/* + * a list of the commands that can be sent to the NOVRAM + */ + +#define EXTEND 0x100 +#define IAWRITE 0x140 +#define IAREAD 0x180 +#define ERASE 0x1c0 + +#define EWDS 0x00 +#define WRAL 0x10 +#define ERAL 0x20 +#define EWEN 0x30 + +/* + * these bits duplicate the hw_flip.h register settings + * note: how the data in / out bits are defined in the flipper specification + */ + +#define NVCE 0x02 +#define NVSK 0x01 +#define NVDO 0x08 +#define NVDI 0x04 +/*********************** + * + * This define ands the value and the current config register and puts + * the result in the config register + * + ***********************/ + +#define CFG_AND(val) { \ + u32 t; \ + t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); \ + t &= (val); \ + writel(t, iadev->reg+IPHASE5575_EEPROM_ACCESS); \ + } + +/*********************** + * + * This define ors the value and the current config register and puts + * the result in the config register + * + ***********************/ + +#define CFG_OR(val) { \ + u32 t; \ + t = readl(iadev->reg+IPHASE5575_EEPROM_ACCESS); \ + t |= (val); \ + writel(t, iadev->reg+IPHASE5575_EEPROM_ACCESS); \ + } + +/*********************** + * + * Send a command to the NOVRAM, the command is in cmd. + * + * clear CE and SK. Then assert CE. + * Clock each of the command bits out in the correct order with SK + * exit with CE still asserted + * + ***********************/ + +#define NVRAM_CMD(cmd) { \ + int i; \ + u_short c = cmd; \ + CFG_AND(~(NVCE|NVSK)); \ + CFG_OR(NVCE); \ + for (i=0; ireg+IPHASE5575_EEPROM_ACCESS); \ + value = (_t & NVDO) ? 1 : 0; \ + } + + +#endif /* IPHASE_H */ diff --git a/drivers/atm/lanai.c b/drivers/atm/lanai.c new file mode 100644 index 000000000..92edd100a --- /dev/null +++ b/drivers/atm/lanai.c @@ -0,0 +1,2604 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* lanai.c -- Copyright 1999-2003 by Mitchell Blank Jr + * + * This driver supports ATM cards based on the Efficient "Lanai" + * chipset such as the Speedstream 3010 and the ENI-25p. The + * Speedstream 3060 is currently not supported since we don't + * have the code to drive the on-board Alcatel DSL chipset (yet). + * + * Thanks to Efficient for supporting this project with hardware, + * documentation, and by answering my questions. + * + * Things not working yet: + * + * o We don't support the Speedstream 3060 yet - this card has + * an on-board DSL modem chip by Alcatel and the driver will + * need some extra code added to handle it + * + * o Note that due to limitations of the Lanai only one VCC can be + * in CBR at once + * + * o We don't currently parse the EEPROM at all. The code is all + * there as per the spec, but it doesn't actually work. I think + * there may be some issues with the docs. Anyway, do NOT + * enable it yet - bugs in that code may actually damage your + * hardware! Because of this you should hardware an ESI before + * trying to use this in a LANE or MPOA environment. + * + * o AAL0 is stubbed in but the actual rx/tx path isn't written yet: + * vcc_tx_aal0() needs to send or queue a SKB + * vcc_tx_unqueue_aal0() needs to attempt to send queued SKBs + * vcc_rx_aal0() needs to handle AAL0 interrupts + * This isn't too much work - I just wanted to get other things + * done first. + * + * o lanai_change_qos() isn't written yet + * + * o There aren't any ioctl's yet -- I'd like to eventually support + * setting loopback and LED modes that way. + * + * o If the segmentation engine or DMA gets shut down we should restart + * card as per section 17.0i. (see lanai_reset) + * + * o setsockopt(SO_CIRANGE) isn't done (although despite what the + * API says it isn't exactly commonly implemented) + */ + +/* Version history: + * v.1.00 -- 26-JUL-2003 -- PCI/DMA updates + * v.0.02 -- 11-JAN-2000 -- Endian fixes + * v.0.01 -- 30-NOV-1999 -- Initial release + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------- TUNABLE PARAMATERS: */ + +/* + * Maximum number of VCIs per card. Setting it lower could theoretically + * save some memory, but since we allocate our vcc list with get_free_pages, + * it's not really likely for most architectures + */ +#define NUM_VCI (1024) + +/* + * Enable extra debugging + */ +#define DEBUG +/* + * Debug _all_ register operations with card, except the memory test. + * Also disables the timed poll to prevent extra chattiness. This + * isn't for normal use + */ +#undef DEBUG_RW + +/* + * The programming guide specifies a full test of the on-board SRAM + * at initialization time. Undefine to remove this + */ +#define FULL_MEMORY_TEST + +/* + * This is the number of (4 byte) service entries that we will + * try to allocate at startup. Note that we will end up with + * one PAGE_SIZE's worth regardless of what this is set to + */ +#define SERVICE_ENTRIES (1024) +/* TODO: make above a module load-time option */ + +/* + * We normally read the onboard EEPROM in order to discover our MAC + * address. Undefine to _not_ do this + */ +/* #define READ_EEPROM */ /* ***DONT ENABLE YET*** */ +/* TODO: make above a module load-time option (also) */ + +/* + * Depth of TX fifo (in 128 byte units; range 2-31) + * Smaller numbers are better for network latency + * Larger numbers are better for PCI latency + * I'm really sure where the best tradeoff is, but the BSD driver uses + * 7 and it seems to work ok. + */ +#define TX_FIFO_DEPTH (7) +/* TODO: make above a module load-time option */ + +/* + * How often (in jiffies) we will try to unstick stuck connections - + * shouldn't need to happen much + */ +#define LANAI_POLL_PERIOD (10*HZ) +/* TODO: make above a module load-time option */ + +/* + * When allocating an AAL5 receiving buffer, try to make it at least + * large enough to hold this many max_sdu sized PDUs + */ +#define AAL5_RX_MULTIPLIER (3) +/* TODO: make above a module load-time option */ + +/* + * Same for transmitting buffer + */ +#define AAL5_TX_MULTIPLIER (3) +/* TODO: make above a module load-time option */ + +/* + * When allocating an AAL0 transmiting buffer, how many cells should fit. + * Remember we'll end up with a PAGE_SIZE of them anyway, so this isn't + * really critical + */ +#define AAL0_TX_MULTIPLIER (40) +/* TODO: make above a module load-time option */ + +/* + * How large should we make the AAL0 receiving buffer. Remember that this + * is shared between all AAL0 VC's + */ +#define AAL0_RX_BUFFER_SIZE (PAGE_SIZE) +/* TODO: make above a module load-time option */ + +/* + * Should we use Lanai's "powerdown" feature when no vcc's are bound? + */ +/* #define USE_POWERDOWN */ +/* TODO: make above a module load-time option (also) */ + +/* -------------------- DEBUGGING AIDS: */ + +#define DEV_LABEL "lanai" + +#ifdef DEBUG + +#define DPRINTK(format, args...) \ + printk(KERN_DEBUG DEV_LABEL ": " format, ##args) +#define APRINTK(truth, format, args...) \ + do { \ + if (unlikely(!(truth))) \ + printk(KERN_ERR DEV_LABEL ": " format, ##args); \ + } while (0) + +#else /* !DEBUG */ + +#define DPRINTK(format, args...) +#define APRINTK(truth, format, args...) + +#endif /* DEBUG */ + +#ifdef DEBUG_RW +#define RWDEBUG(format, args...) \ + printk(KERN_DEBUG DEV_LABEL ": " format, ##args) +#else /* !DEBUG_RW */ +#define RWDEBUG(format, args...) +#endif + +/* -------------------- DATA DEFINITIONS: */ + +#define LANAI_MAPPING_SIZE (0x40000) +#define LANAI_EEPROM_SIZE (128) + +typedef int vci_t; +typedef void __iomem *bus_addr_t; + +/* DMA buffer in host memory for TX, RX, or service list. */ +struct lanai_buffer { + u32 *start; /* From get_free_pages */ + u32 *end; /* One past last byte */ + u32 *ptr; /* Pointer to current host location */ + dma_addr_t dmaaddr; +}; + +struct lanai_vcc_stats { + unsigned rx_nomem; + union { + struct { + unsigned rx_badlen; + unsigned service_trash; + unsigned service_stream; + unsigned service_rxcrc; + } aal5; + struct { + } aal0; + } x; +}; + +struct lanai_dev; /* Forward declaration */ + +/* + * This is the card-specific per-vcc data. Note that unlike some other + * drivers there is NOT a 1-to-1 correspondance between these and + * atm_vcc's - each one of these represents an actual 2-way vcc, but + * an atm_vcc can be 1-way and share with a 1-way vcc in the other + * direction. To make it weirder, there can even be 0-way vccs + * bound to us, waiting to do a change_qos + */ +struct lanai_vcc { + bus_addr_t vbase; /* Base of VCC's registers */ + struct lanai_vcc_stats stats; + int nref; /* # of atm_vcc's who reference us */ + vci_t vci; + struct { + struct lanai_buffer buf; + struct atm_vcc *atmvcc; /* atm_vcc who is receiver */ + } rx; + struct { + struct lanai_buffer buf; + struct atm_vcc *atmvcc; /* atm_vcc who is transmitter */ + int endptr; /* last endptr from service entry */ + struct sk_buff_head backlog; + void (*unqueue)(struct lanai_dev *, struct lanai_vcc *, int); + } tx; +}; + +enum lanai_type { + lanai2 = PCI_DEVICE_ID_EF_ATM_LANAI2, + lanaihb = PCI_DEVICE_ID_EF_ATM_LANAIHB +}; + +struct lanai_dev_stats { + unsigned ovfl_trash; /* # of cells dropped - buffer overflow */ + unsigned vci_trash; /* # of cells dropped - closed vci */ + unsigned hec_err; /* # of cells dropped - bad HEC */ + unsigned atm_ovfl; /* # of cells dropped - rx fifo overflow */ + unsigned pcierr_parity_detect; + unsigned pcierr_serr_set; + unsigned pcierr_master_abort; + unsigned pcierr_m_target_abort; + unsigned pcierr_s_target_abort; + unsigned pcierr_master_parity; + unsigned service_notx; + unsigned service_norx; + unsigned service_rxnotaal5; + unsigned dma_reenable; + unsigned card_reset; +}; + +struct lanai_dev { + bus_addr_t base; + struct lanai_dev_stats stats; + struct lanai_buffer service; + struct lanai_vcc **vccs; +#ifdef USE_POWERDOWN + int nbound; /* number of bound vccs */ +#endif + enum lanai_type type; + vci_t num_vci; /* Currently just NUM_VCI */ + u8 eeprom[LANAI_EEPROM_SIZE]; + u32 serialno, magicno; + struct pci_dev *pci; + DECLARE_BITMAP(backlog_vccs, NUM_VCI); /* VCCs with tx backlog */ + DECLARE_BITMAP(transmit_ready, NUM_VCI); /* VCCs with transmit space */ + struct timer_list timer; + int naal0; + struct lanai_buffer aal0buf; /* AAL0 RX buffers */ + u32 conf1, conf2; /* CONFIG[12] registers */ + u32 status; /* STATUS register */ + spinlock_t endtxlock; + spinlock_t servicelock; + struct atm_vcc *cbrvcc; + int number; + int board_rev; +/* TODO - look at race conditions with maintence of conf1/conf2 */ +/* TODO - transmit locking: should we use _irq not _irqsave? */ +/* TODO - organize above in some rational fashion (see ) */ +}; + +/* + * Each device has two bitmaps for each VCC (baclog_vccs and transmit_ready) + * This function iterates one of these, calling a given function for each + * vci with their bit set + */ +static void vci_bitfield_iterate(struct lanai_dev *lanai, + const unsigned long *lp, + void (*func)(struct lanai_dev *,vci_t vci)) +{ + vci_t vci; + + for_each_set_bit(vci, lp, NUM_VCI) + func(lanai, vci); +} + +/* -------------------- BUFFER UTILITIES: */ + +/* + * Lanai needs DMA buffers aligned to 256 bytes of at least 1024 bytes - + * usually any page allocation will do. Just to be safe in case + * PAGE_SIZE is insanely tiny, though... + */ +#define LANAI_PAGE_SIZE ((PAGE_SIZE >= 1024) ? PAGE_SIZE : 1024) + +/* + * Allocate a buffer in host RAM for service list, RX, or TX + * Returns buf->start==NULL if no memory + * Note that the size will be rounded up 2^n bytes, and + * if we can't allocate that we'll settle for something smaller + * until minbytes + */ +static void lanai_buf_allocate(struct lanai_buffer *buf, + size_t bytes, size_t minbytes, struct pci_dev *pci) +{ + int size; + + if (bytes > (128 * 1024)) /* max lanai buffer size */ + bytes = 128 * 1024; + for (size = LANAI_PAGE_SIZE; size < bytes; size *= 2) + ; + if (minbytes < LANAI_PAGE_SIZE) + minbytes = LANAI_PAGE_SIZE; + do { + /* + * Technically we could use non-consistent mappings for + * everything, but the way the lanai uses DMA memory would + * make that a terrific pain. This is much simpler. + */ + buf->start = dma_alloc_coherent(&pci->dev, + size, &buf->dmaaddr, GFP_KERNEL); + if (buf->start != NULL) { /* Success */ + /* Lanai requires 256-byte alignment of DMA bufs */ + APRINTK((buf->dmaaddr & ~0xFFFFFF00) == 0, + "bad dmaaddr: 0x%lx\n", + (unsigned long) buf->dmaaddr); + buf->ptr = buf->start; + buf->end = (u32 *) + (&((unsigned char *) buf->start)[size]); + memset(buf->start, 0, size); + break; + } + size /= 2; + } while (size >= minbytes); +} + +/* size of buffer in bytes */ +static inline size_t lanai_buf_size(const struct lanai_buffer *buf) +{ + return ((unsigned long) buf->end) - ((unsigned long) buf->start); +} + +static void lanai_buf_deallocate(struct lanai_buffer *buf, + struct pci_dev *pci) +{ + if (buf->start != NULL) { + dma_free_coherent(&pci->dev, lanai_buf_size(buf), + buf->start, buf->dmaaddr); + buf->start = buf->end = buf->ptr = NULL; + } +} + +/* size of buffer as "card order" (0=1k .. 7=128k) */ +static int lanai_buf_size_cardorder(const struct lanai_buffer *buf) +{ + int order = get_order(lanai_buf_size(buf)) + (PAGE_SHIFT - 10); + + /* This can only happen if PAGE_SIZE is gigantic, but just in case */ + if (order > 7) + order = 7; + return order; +} + +/* -------------------- PORT I/O UTILITIES: */ + +/* Registers (and their bit-fields) */ +enum lanai_register { + Reset_Reg = 0x00, /* Reset; read for chip type; bits: */ +#define RESET_GET_BOARD_REV(x) (((x)>> 0)&0x03) /* Board revision */ +#define RESET_GET_BOARD_ID(x) (((x)>> 2)&0x03) /* Board ID */ +#define BOARD_ID_LANAI256 (0) /* 25.6M adapter card */ + Endian_Reg = 0x04, /* Endian setting */ + IntStatus_Reg = 0x08, /* Interrupt status */ + IntStatusMasked_Reg = 0x0C, /* Interrupt status (masked) */ + IntAck_Reg = 0x10, /* Interrupt acknowledge */ + IntAckMasked_Reg = 0x14, /* Interrupt acknowledge (masked) */ + IntStatusSet_Reg = 0x18, /* Get status + enable/disable */ + IntStatusSetMasked_Reg = 0x1C, /* Get status + en/di (masked) */ + IntControlEna_Reg = 0x20, /* Interrupt control enable */ + IntControlDis_Reg = 0x24, /* Interrupt control disable */ + Status_Reg = 0x28, /* Status */ +#define STATUS_PROMDATA (0x00000001) /* PROM_DATA pin */ +#define STATUS_WAITING (0x00000002) /* Interrupt being delayed */ +#define STATUS_SOOL (0x00000004) /* SOOL alarm */ +#define STATUS_LOCD (0x00000008) /* LOCD alarm */ +#define STATUS_LED (0x00000010) /* LED (HAPPI) output */ +#define STATUS_GPIN (0x00000020) /* GPIN pin */ +#define STATUS_BUTTBUSY (0x00000040) /* Butt register is pending */ + Config1_Reg = 0x2C, /* Config word 1; bits: */ +#define CONFIG1_PROMDATA (0x00000001) /* PROM_DATA pin */ +#define CONFIG1_PROMCLK (0x00000002) /* PROM_CLK pin */ +#define CONFIG1_SET_READMODE(x) ((x)*0x004) /* PCI BM reads; values: */ +#define READMODE_PLAIN (0) /* Plain memory read */ +#define READMODE_LINE (2) /* Memory read line */ +#define READMODE_MULTIPLE (3) /* Memory read multiple */ +#define CONFIG1_DMA_ENABLE (0x00000010) /* Turn on DMA */ +#define CONFIG1_POWERDOWN (0x00000020) /* Turn off clocks */ +#define CONFIG1_SET_LOOPMODE(x) ((x)*0x080) /* Clock&loop mode; values: */ +#define LOOPMODE_NORMAL (0) /* Normal - no loop */ +#define LOOPMODE_TIME (1) +#define LOOPMODE_DIAG (2) +#define LOOPMODE_LINE (3) +#define CONFIG1_MASK_LOOPMODE (0x00000180) +#define CONFIG1_SET_LEDMODE(x) ((x)*0x0200) /* Mode of LED; values: */ +#define LEDMODE_NOT_SOOL (0) /* !SOOL */ +#define LEDMODE_OFF (1) /* 0 */ +#define LEDMODE_ON (2) /* 1 */ +#define LEDMODE_NOT_LOCD (3) /* !LOCD */ +#define LEDMORE_GPIN (4) /* GPIN */ +#define LEDMODE_NOT_GPIN (7) /* !GPIN */ +#define CONFIG1_MASK_LEDMODE (0x00000E00) +#define CONFIG1_GPOUT1 (0x00001000) /* Toggle for reset */ +#define CONFIG1_GPOUT2 (0x00002000) /* Loopback PHY */ +#define CONFIG1_GPOUT3 (0x00004000) /* Loopback lanai */ + Config2_Reg = 0x30, /* Config word 2; bits: */ +#define CONFIG2_HOWMANY (0x00000001) /* >512 VCIs? */ +#define CONFIG2_PTI7_MODE (0x00000002) /* Make PTI=7 RM, not OAM */ +#define CONFIG2_VPI_CHK_DIS (0x00000004) /* Ignore RX VPI value */ +#define CONFIG2_HEC_DROP (0x00000008) /* Drop cells w/ HEC errors */ +#define CONFIG2_VCI0_NORMAL (0x00000010) /* Treat VCI=0 normally */ +#define CONFIG2_CBR_ENABLE (0x00000020) /* Deal with CBR traffic */ +#define CONFIG2_TRASH_ALL (0x00000040) /* Trashing incoming cells */ +#define CONFIG2_TX_DISABLE (0x00000080) /* Trashing outgoing cells */ +#define CONFIG2_SET_TRASH (0x00000100) /* Turn trashing on */ + Statistics_Reg = 0x34, /* Statistics; bits: */ +#define STATS_GET_FIFO_OVFL(x) (((x)>> 0)&0xFF) /* FIFO overflowed */ +#define STATS_GET_HEC_ERR(x) (((x)>> 8)&0xFF) /* HEC was bad */ +#define STATS_GET_BAD_VCI(x) (((x)>>16)&0xFF) /* VCI not open */ +#define STATS_GET_BUF_OVFL(x) (((x)>>24)&0xFF) /* VCC buffer full */ + ServiceStuff_Reg = 0x38, /* Service stuff; bits: */ +#define SSTUFF_SET_SIZE(x) ((x)*0x20000000) /* size of service buffer */ +#define SSTUFF_SET_ADDR(x) ((x)>>8) /* set address of buffer */ + ServWrite_Reg = 0x3C, /* ServWrite Pointer */ + ServRead_Reg = 0x40, /* ServRead Pointer */ + TxDepth_Reg = 0x44, /* FIFO Transmit Depth */ + Butt_Reg = 0x48, /* Butt register */ + CBR_ICG_Reg = 0x50, + CBR_PTR_Reg = 0x54, + PingCount_Reg = 0x58, /* Ping count */ + DMA_Addr_Reg = 0x5C /* DMA address */ +}; + +static inline bus_addr_t reg_addr(const struct lanai_dev *lanai, + enum lanai_register reg) +{ + return lanai->base + reg; +} + +static inline u32 reg_read(const struct lanai_dev *lanai, + enum lanai_register reg) +{ + u32 t; + t = readl(reg_addr(lanai, reg)); + RWDEBUG("R [0x%08X] 0x%02X = 0x%08X\n", (unsigned int) lanai->base, + (int) reg, t); + return t; +} + +static inline void reg_write(const struct lanai_dev *lanai, u32 val, + enum lanai_register reg) +{ + RWDEBUG("W [0x%08X] 0x%02X < 0x%08X\n", (unsigned int) lanai->base, + (int) reg, val); + writel(val, reg_addr(lanai, reg)); +} + +static inline void conf1_write(const struct lanai_dev *lanai) +{ + reg_write(lanai, lanai->conf1, Config1_Reg); +} + +static inline void conf2_write(const struct lanai_dev *lanai) +{ + reg_write(lanai, lanai->conf2, Config2_Reg); +} + +/* Same as conf2_write(), but defers I/O if we're powered down */ +static inline void conf2_write_if_powerup(const struct lanai_dev *lanai) +{ +#ifdef USE_POWERDOWN + if (unlikely((lanai->conf1 & CONFIG1_POWERDOWN) != 0)) + return; +#endif /* USE_POWERDOWN */ + conf2_write(lanai); +} + +static inline void reset_board(const struct lanai_dev *lanai) +{ + DPRINTK("about to reset board\n"); + reg_write(lanai, 0, Reset_Reg); + /* + * If we don't delay a little while here then we can end up + * leaving the card in a VERY weird state and lock up the + * PCI bus. This isn't documented anywhere but I've convinced + * myself after a lot of painful experimentation + */ + udelay(5); +} + +/* -------------------- CARD SRAM UTILITIES: */ + +/* The SRAM is mapped into normal PCI memory space - the only catch is + * that it is only 16-bits wide but must be accessed as 32-bit. The + * 16 high bits will be zero. We don't hide this, since they get + * programmed mostly like discrete registers anyway + */ +#define SRAM_START (0x20000) +#define SRAM_BYTES (0x20000) /* Again, half don't really exist */ + +static inline bus_addr_t sram_addr(const struct lanai_dev *lanai, int offset) +{ + return lanai->base + SRAM_START + offset; +} + +static inline u32 sram_read(const struct lanai_dev *lanai, int offset) +{ + return readl(sram_addr(lanai, offset)); +} + +static inline void sram_write(const struct lanai_dev *lanai, + u32 val, int offset) +{ + writel(val, sram_addr(lanai, offset)); +} + +static int sram_test_word(const struct lanai_dev *lanai, int offset, + u32 pattern) +{ + u32 readback; + sram_write(lanai, pattern, offset); + readback = sram_read(lanai, offset); + if (likely(readback == pattern)) + return 0; + printk(KERN_ERR DEV_LABEL + "(itf %d): SRAM word at %d bad: wrote 0x%X, read 0x%X\n", + lanai->number, offset, + (unsigned int) pattern, (unsigned int) readback); + return -EIO; +} + +static int sram_test_pass(const struct lanai_dev *lanai, u32 pattern) +{ + int offset, result = 0; + for (offset = 0; offset < SRAM_BYTES && result == 0; offset += 4) + result = sram_test_word(lanai, offset, pattern); + return result; +} + +static int sram_test_and_clear(const struct lanai_dev *lanai) +{ +#ifdef FULL_MEMORY_TEST + int result; + DPRINTK("testing SRAM\n"); + if ((result = sram_test_pass(lanai, 0x5555)) != 0) + return result; + if ((result = sram_test_pass(lanai, 0xAAAA)) != 0) + return result; +#endif + DPRINTK("clearing SRAM\n"); + return sram_test_pass(lanai, 0x0000); +} + +/* -------------------- CARD-BASED VCC TABLE UTILITIES: */ + +/* vcc table */ +enum lanai_vcc_offset { + vcc_rxaddr1 = 0x00, /* Location1, plus bits: */ +#define RXADDR1_SET_SIZE(x) ((x)*0x0000100) /* size of RX buffer */ +#define RXADDR1_SET_RMMODE(x) ((x)*0x00800) /* RM cell action; values: */ +#define RMMODE_TRASH (0) /* discard */ +#define RMMODE_PRESERVE (1) /* input as AAL0 */ +#define RMMODE_PIPE (2) /* pipe to coscheduler */ +#define RMMODE_PIPEALL (3) /* pipe non-RM too */ +#define RXADDR1_OAM_PRESERVE (0x00002000) /* Input OAM cells as AAL0 */ +#define RXADDR1_SET_MODE(x) ((x)*0x0004000) /* Reassembly mode */ +#define RXMODE_TRASH (0) /* discard */ +#define RXMODE_AAL0 (1) /* non-AAL5 mode */ +#define RXMODE_AAL5 (2) /* AAL5, intr. each PDU */ +#define RXMODE_AAL5_STREAM (3) /* AAL5 w/o per-PDU intr */ + vcc_rxaddr2 = 0x04, /* Location2 */ + vcc_rxcrc1 = 0x08, /* RX CRC claculation space */ + vcc_rxcrc2 = 0x0C, + vcc_rxwriteptr = 0x10, /* RX writeptr, plus bits: */ +#define RXWRITEPTR_LASTEFCI (0x00002000) /* Last PDU had EFCI bit */ +#define RXWRITEPTR_DROPPING (0x00004000) /* Had error, dropping */ +#define RXWRITEPTR_TRASHING (0x00008000) /* Trashing */ + vcc_rxbufstart = 0x14, /* RX bufstart, plus bits: */ +#define RXBUFSTART_CLP (0x00004000) +#define RXBUFSTART_CI (0x00008000) + vcc_rxreadptr = 0x18, /* RX readptr */ + vcc_txicg = 0x1C, /* TX ICG */ + vcc_txaddr1 = 0x20, /* Location1, plus bits: */ +#define TXADDR1_SET_SIZE(x) ((x)*0x0000100) /* size of TX buffer */ +#define TXADDR1_ABR (0x00008000) /* use ABR (doesn't work) */ + vcc_txaddr2 = 0x24, /* Location2 */ + vcc_txcrc1 = 0x28, /* TX CRC claculation space */ + vcc_txcrc2 = 0x2C, + vcc_txreadptr = 0x30, /* TX Readptr, plus bits: */ +#define TXREADPTR_GET_PTR(x) ((x)&0x01FFF) +#define TXREADPTR_MASK_DELTA (0x0000E000) /* ? */ + vcc_txendptr = 0x34, /* TX Endptr, plus bits: */ +#define TXENDPTR_CLP (0x00002000) +#define TXENDPTR_MASK_PDUMODE (0x0000C000) /* PDU mode; values: */ +#define PDUMODE_AAL0 (0*0x04000) +#define PDUMODE_AAL5 (2*0x04000) +#define PDUMODE_AAL5STREAM (3*0x04000) + vcc_txwriteptr = 0x38, /* TX Writeptr */ +#define TXWRITEPTR_GET_PTR(x) ((x)&0x1FFF) + vcc_txcbr_next = 0x3C /* # of next CBR VCI in ring */ +#define TXCBR_NEXT_BOZO (0x00008000) /* "bozo bit" */ +}; + +#define CARDVCC_SIZE (0x40) + +static inline bus_addr_t cardvcc_addr(const struct lanai_dev *lanai, + vci_t vci) +{ + return sram_addr(lanai, vci * CARDVCC_SIZE); +} + +static inline u32 cardvcc_read(const struct lanai_vcc *lvcc, + enum lanai_vcc_offset offset) +{ + u32 val; + APRINTK(lvcc->vbase != NULL, "cardvcc_read: unbound vcc!\n"); + val= readl(lvcc->vbase + offset); + RWDEBUG("VR vci=%04d 0x%02X = 0x%08X\n", + lvcc->vci, (int) offset, val); + return val; +} + +static inline void cardvcc_write(const struct lanai_vcc *lvcc, + u32 val, enum lanai_vcc_offset offset) +{ + APRINTK(lvcc->vbase != NULL, "cardvcc_write: unbound vcc!\n"); + APRINTK((val & ~0xFFFF) == 0, + "cardvcc_write: bad val 0x%X (vci=%d, addr=0x%02X)\n", + (unsigned int) val, lvcc->vci, (unsigned int) offset); + RWDEBUG("VW vci=%04d 0x%02X > 0x%08X\n", + lvcc->vci, (unsigned int) offset, (unsigned int) val); + writel(val, lvcc->vbase + offset); +} + +/* -------------------- COMPUTE SIZE OF AN AAL5 PDU: */ + +/* How many bytes will an AAL5 PDU take to transmit - remember that: + * o we need to add 8 bytes for length, CPI, UU, and CRC + * o we need to round up to 48 bytes for cells + */ +static inline int aal5_size(int size) +{ + int cells = (size + 8 + 47) / 48; + return cells * 48; +} + +/* -------------------- FREE AN ATM SKB: */ + +static inline void lanai_free_skb(struct atm_vcc *atmvcc, struct sk_buff *skb) +{ + if (atmvcc->pop != NULL) + atmvcc->pop(atmvcc, skb); + else + dev_kfree_skb_any(skb); +} + +/* -------------------- TURN VCCS ON AND OFF: */ + +static void host_vcc_start_rx(const struct lanai_vcc *lvcc) +{ + u32 addr1; + if (lvcc->rx.atmvcc->qos.aal == ATM_AAL5) { + dma_addr_t dmaaddr = lvcc->rx.buf.dmaaddr; + cardvcc_write(lvcc, 0xFFFF, vcc_rxcrc1); + cardvcc_write(lvcc, 0xFFFF, vcc_rxcrc2); + cardvcc_write(lvcc, 0, vcc_rxwriteptr); + cardvcc_write(lvcc, 0, vcc_rxbufstart); + cardvcc_write(lvcc, 0, vcc_rxreadptr); + cardvcc_write(lvcc, (dmaaddr >> 16) & 0xFFFF, vcc_rxaddr2); + addr1 = ((dmaaddr >> 8) & 0xFF) | + RXADDR1_SET_SIZE(lanai_buf_size_cardorder(&lvcc->rx.buf))| + RXADDR1_SET_RMMODE(RMMODE_TRASH) | /* ??? */ + /* RXADDR1_OAM_PRESERVE | --- no OAM support yet */ + RXADDR1_SET_MODE(RXMODE_AAL5); + } else + addr1 = RXADDR1_SET_RMMODE(RMMODE_PRESERVE) | /* ??? */ + RXADDR1_OAM_PRESERVE | /* ??? */ + RXADDR1_SET_MODE(RXMODE_AAL0); + /* This one must be last! */ + cardvcc_write(lvcc, addr1, vcc_rxaddr1); +} + +static void host_vcc_start_tx(const struct lanai_vcc *lvcc) +{ + dma_addr_t dmaaddr = lvcc->tx.buf.dmaaddr; + cardvcc_write(lvcc, 0, vcc_txicg); + cardvcc_write(lvcc, 0xFFFF, vcc_txcrc1); + cardvcc_write(lvcc, 0xFFFF, vcc_txcrc2); + cardvcc_write(lvcc, 0, vcc_txreadptr); + cardvcc_write(lvcc, 0, vcc_txendptr); + cardvcc_write(lvcc, 0, vcc_txwriteptr); + cardvcc_write(lvcc, + (lvcc->tx.atmvcc->qos.txtp.traffic_class == ATM_CBR) ? + TXCBR_NEXT_BOZO | lvcc->vci : 0, vcc_txcbr_next); + cardvcc_write(lvcc, (dmaaddr >> 16) & 0xFFFF, vcc_txaddr2); + cardvcc_write(lvcc, + ((dmaaddr >> 8) & 0xFF) | + TXADDR1_SET_SIZE(lanai_buf_size_cardorder(&lvcc->tx.buf)), + vcc_txaddr1); +} + +/* Shutdown receiving on card */ +static void lanai_shutdown_rx_vci(const struct lanai_vcc *lvcc) +{ + if (lvcc->vbase == NULL) /* We were never bound to a VCI */ + return; + /* 15.1.1 - set to trashing, wait one cell time (15us) */ + cardvcc_write(lvcc, + RXADDR1_SET_RMMODE(RMMODE_TRASH) | + RXADDR1_SET_MODE(RXMODE_TRASH), vcc_rxaddr1); + udelay(15); + /* 15.1.2 - clear rest of entries */ + cardvcc_write(lvcc, 0, vcc_rxaddr2); + cardvcc_write(lvcc, 0, vcc_rxcrc1); + cardvcc_write(lvcc, 0, vcc_rxcrc2); + cardvcc_write(lvcc, 0, vcc_rxwriteptr); + cardvcc_write(lvcc, 0, vcc_rxbufstart); + cardvcc_write(lvcc, 0, vcc_rxreadptr); +} + +/* Shutdown transmitting on card. + * Unfortunately the lanai needs us to wait until all the data + * drains out of the buffer before we can dealloc it, so this + * can take awhile -- up to 370ms for a full 128KB buffer + * assuming everone else is quiet. In theory the time is + * boundless if there's a CBR VCC holding things up. + */ +static void lanai_shutdown_tx_vci(struct lanai_dev *lanai, + struct lanai_vcc *lvcc) +{ + struct sk_buff *skb; + unsigned long flags, timeout; + int read, write, lastread = -1; + APRINTK(!in_interrupt(), + "lanai_shutdown_tx_vci called w/o process context!\n"); + if (lvcc->vbase == NULL) /* We were never bound to a VCI */ + return; + /* 15.2.1 - wait for queue to drain */ + while ((skb = skb_dequeue(&lvcc->tx.backlog)) != NULL) + lanai_free_skb(lvcc->tx.atmvcc, skb); + read_lock_irqsave(&vcc_sklist_lock, flags); + __clear_bit(lvcc->vci, lanai->backlog_vccs); + read_unlock_irqrestore(&vcc_sklist_lock, flags); + /* + * We need to wait for the VCC to drain but don't wait forever. We + * give each 1K of buffer size 1/128th of a second to clear out. + * TODO: maybe disable CBR if we're about to timeout? + */ + timeout = jiffies + + (((lanai_buf_size(&lvcc->tx.buf) / 1024) * HZ) >> 7); + write = TXWRITEPTR_GET_PTR(cardvcc_read(lvcc, vcc_txwriteptr)); + for (;;) { + read = TXREADPTR_GET_PTR(cardvcc_read(lvcc, vcc_txreadptr)); + if (read == write && /* Is TX buffer empty? */ + (lvcc->tx.atmvcc->qos.txtp.traffic_class != ATM_CBR || + (cardvcc_read(lvcc, vcc_txcbr_next) & + TXCBR_NEXT_BOZO) == 0)) + break; + if (read != lastread) { /* Has there been any progress? */ + lastread = read; + timeout += HZ / 10; + } + if (unlikely(time_after(jiffies, timeout))) { + printk(KERN_ERR DEV_LABEL "(itf %d): Timed out on " + "backlog closing vci %d\n", + lvcc->tx.atmvcc->dev->number, lvcc->vci); + DPRINTK("read, write = %d, %d\n", read, write); + break; + } + msleep(40); + } + /* 15.2.2 - clear out all tx registers */ + cardvcc_write(lvcc, 0, vcc_txreadptr); + cardvcc_write(lvcc, 0, vcc_txwriteptr); + cardvcc_write(lvcc, 0, vcc_txendptr); + cardvcc_write(lvcc, 0, vcc_txcrc1); + cardvcc_write(lvcc, 0, vcc_txcrc2); + cardvcc_write(lvcc, 0, vcc_txaddr2); + cardvcc_write(lvcc, 0, vcc_txaddr1); +} + +/* -------------------- MANAGING AAL0 RX BUFFER: */ + +static inline int aal0_buffer_allocate(struct lanai_dev *lanai) +{ + DPRINTK("aal0_buffer_allocate: allocating AAL0 RX buffer\n"); + lanai_buf_allocate(&lanai->aal0buf, AAL0_RX_BUFFER_SIZE, 80, + lanai->pci); + return (lanai->aal0buf.start == NULL) ? -ENOMEM : 0; +} + +static inline void aal0_buffer_free(struct lanai_dev *lanai) +{ + DPRINTK("aal0_buffer_allocate: freeing AAL0 RX buffer\n"); + lanai_buf_deallocate(&lanai->aal0buf, lanai->pci); +} + +/* -------------------- EEPROM UTILITIES: */ + +/* Offsets of data in the EEPROM */ +#define EEPROM_COPYRIGHT (0) +#define EEPROM_COPYRIGHT_LEN (44) +#define EEPROM_CHECKSUM (62) +#define EEPROM_CHECKSUM_REV (63) +#define EEPROM_MAC (64) +#define EEPROM_MAC_REV (70) +#define EEPROM_SERIAL (112) +#define EEPROM_SERIAL_REV (116) +#define EEPROM_MAGIC (120) +#define EEPROM_MAGIC_REV (124) + +#define EEPROM_MAGIC_VALUE (0x5AB478D2) + +#ifndef READ_EEPROM + +/* Stub functions to use if EEPROM reading is disabled */ +static int eeprom_read(struct lanai_dev *lanai) +{ + printk(KERN_INFO DEV_LABEL "(itf %d): *NOT* reading EEPROM\n", + lanai->number); + memset(&lanai->eeprom[EEPROM_MAC], 0, 6); + return 0; +} + +static int eeprom_validate(struct lanai_dev *lanai) +{ + lanai->serialno = 0; + lanai->magicno = EEPROM_MAGIC_VALUE; + return 0; +} + +#else /* READ_EEPROM */ + +static int eeprom_read(struct lanai_dev *lanai) +{ + int i, address; + u8 data; + u32 tmp; +#define set_config1(x) do { lanai->conf1 = x; conf1_write(lanai); \ + } while (0) +#define clock_h() set_config1(lanai->conf1 | CONFIG1_PROMCLK) +#define clock_l() set_config1(lanai->conf1 &~ CONFIG1_PROMCLK) +#define data_h() set_config1(lanai->conf1 | CONFIG1_PROMDATA) +#define data_l() set_config1(lanai->conf1 &~ CONFIG1_PROMDATA) +#define pre_read() do { data_h(); clock_h(); udelay(5); } while (0) +#define read_pin() (reg_read(lanai, Status_Reg) & STATUS_PROMDATA) +#define send_stop() do { data_l(); udelay(5); clock_h(); udelay(5); \ + data_h(); udelay(5); } while (0) + /* start with both clock and data high */ + data_h(); clock_h(); udelay(5); + for (address = 0; address < LANAI_EEPROM_SIZE; address++) { + data = (address << 1) | 1; /* Command=read + address */ + /* send start bit */ + data_l(); udelay(5); + clock_l(); udelay(5); + for (i = 128; i != 0; i >>= 1) { /* write command out */ + tmp = (lanai->conf1 & ~CONFIG1_PROMDATA) | + ((data & i) ? CONFIG1_PROMDATA : 0); + if (lanai->conf1 != tmp) { + set_config1(tmp); + udelay(5); /* Let new data settle */ + } + clock_h(); udelay(5); clock_l(); udelay(5); + } + /* look for ack */ + data_h(); clock_h(); udelay(5); + if (read_pin() != 0) + goto error; /* No ack seen */ + clock_l(); udelay(5); + /* read back result */ + for (data = 0, i = 7; i >= 0; i--) { + data_h(); clock_h(); udelay(5); + data = (data << 1) | !!read_pin(); + clock_l(); udelay(5); + } + /* look again for ack */ + data_h(); clock_h(); udelay(5); + if (read_pin() == 0) + goto error; /* Spurious ack */ + clock_l(); udelay(5); + send_stop(); + lanai->eeprom[address] = data; + DPRINTK("EEPROM 0x%04X %02X\n", + (unsigned int) address, (unsigned int) data); + } + return 0; + error: + clock_l(); udelay(5); /* finish read */ + send_stop(); + printk(KERN_ERR DEV_LABEL "(itf %d): error reading EEPROM byte %d\n", + lanai->number, address); + return -EIO; +#undef set_config1 +#undef clock_h +#undef clock_l +#undef data_h +#undef data_l +#undef pre_read +#undef read_pin +#undef send_stop +} + +/* read a big-endian 4-byte value out of eeprom */ +static inline u32 eeprom_be4(const struct lanai_dev *lanai, int address) +{ + return be32_to_cpup((const u32 *) &lanai->eeprom[address]); +} + +/* Checksum/validate EEPROM contents */ +static int eeprom_validate(struct lanai_dev *lanai) +{ + int i, s; + u32 v; + const u8 *e = lanai->eeprom; +#ifdef DEBUG + /* First, see if we can get an ASCIIZ string out of the copyright */ + for (i = EEPROM_COPYRIGHT; + i < (EEPROM_COPYRIGHT + EEPROM_COPYRIGHT_LEN); i++) + if (e[i] < 0x20 || e[i] > 0x7E) + break; + if ( i != EEPROM_COPYRIGHT && + i != EEPROM_COPYRIGHT + EEPROM_COPYRIGHT_LEN && e[i] == '\0') + DPRINTK("eeprom: copyright = \"%s\"\n", + (char *) &e[EEPROM_COPYRIGHT]); + else + DPRINTK("eeprom: copyright not found\n"); +#endif + /* Validate checksum */ + for (i = s = 0; i < EEPROM_CHECKSUM; i++) + s += e[i]; + s &= 0xFF; + if (s != e[EEPROM_CHECKSUM]) { + printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM checksum bad " + "(wanted 0x%02X, got 0x%02X)\n", lanai->number, + (unsigned int) s, (unsigned int) e[EEPROM_CHECKSUM]); + return -EIO; + } + s ^= 0xFF; + if (s != e[EEPROM_CHECKSUM_REV]) { + printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM inverse checksum " + "bad (wanted 0x%02X, got 0x%02X)\n", lanai->number, + (unsigned int) s, (unsigned int) e[EEPROM_CHECKSUM_REV]); + return -EIO; + } + /* Verify MAC address */ + for (i = 0; i < 6; i++) + if ((e[EEPROM_MAC + i] ^ e[EEPROM_MAC_REV + i]) != 0xFF) { + printk(KERN_ERR DEV_LABEL + "(itf %d) : EEPROM MAC addresses don't match " + "(0x%02X, inverse 0x%02X)\n", lanai->number, + (unsigned int) e[EEPROM_MAC + i], + (unsigned int) e[EEPROM_MAC_REV + i]); + return -EIO; + } + DPRINTK("eeprom: MAC address = %pM\n", &e[EEPROM_MAC]); + /* Verify serial number */ + lanai->serialno = eeprom_be4(lanai, EEPROM_SERIAL); + v = eeprom_be4(lanai, EEPROM_SERIAL_REV); + if ((lanai->serialno ^ v) != 0xFFFFFFFF) { + printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM serial numbers " + "don't match (0x%08X, inverse 0x%08X)\n", lanai->number, + (unsigned int) lanai->serialno, (unsigned int) v); + return -EIO; + } + DPRINTK("eeprom: Serial number = %d\n", (unsigned int) lanai->serialno); + /* Verify magic number */ + lanai->magicno = eeprom_be4(lanai, EEPROM_MAGIC); + v = eeprom_be4(lanai, EEPROM_MAGIC_REV); + if ((lanai->magicno ^ v) != 0xFFFFFFFF) { + printk(KERN_ERR DEV_LABEL "(itf %d): EEPROM magic numbers " + "don't match (0x%08X, inverse 0x%08X)\n", lanai->number, + lanai->magicno, v); + return -EIO; + } + DPRINTK("eeprom: Magic number = 0x%08X\n", lanai->magicno); + if (lanai->magicno != EEPROM_MAGIC_VALUE) + printk(KERN_WARNING DEV_LABEL "(itf %d): warning - EEPROM " + "magic not what expected (got 0x%08X, not 0x%08X)\n", + lanai->number, (unsigned int) lanai->magicno, + (unsigned int) EEPROM_MAGIC_VALUE); + return 0; +} + +#endif /* READ_EEPROM */ + +static inline const u8 *eeprom_mac(const struct lanai_dev *lanai) +{ + return &lanai->eeprom[EEPROM_MAC]; +} + +/* -------------------- INTERRUPT HANDLING UTILITIES: */ + +/* Interrupt types */ +#define INT_STATS (0x00000002) /* Statistics counter overflow */ +#define INT_SOOL (0x00000004) /* SOOL changed state */ +#define INT_LOCD (0x00000008) /* LOCD changed state */ +#define INT_LED (0x00000010) /* LED (HAPPI) changed state */ +#define INT_GPIN (0x00000020) /* GPIN changed state */ +#define INT_PING (0x00000040) /* PING_COUNT fulfilled */ +#define INT_WAKE (0x00000080) /* Lanai wants bus */ +#define INT_CBR0 (0x00000100) /* CBR sched hit VCI 0 */ +#define INT_LOCK (0x00000200) /* Service list overflow */ +#define INT_MISMATCH (0x00000400) /* TX magic list mismatch */ +#define INT_AAL0_STR (0x00000800) /* Non-AAL5 buffer half filled */ +#define INT_AAL0 (0x00001000) /* Non-AAL5 data available */ +#define INT_SERVICE (0x00002000) /* Service list entries available */ +#define INT_TABORTSENT (0x00004000) /* Target abort sent by lanai */ +#define INT_TABORTBM (0x00008000) /* Abort rcv'd as bus master */ +#define INT_TIMEOUTBM (0x00010000) /* No response to bus master */ +#define INT_PCIPARITY (0x00020000) /* Parity error on PCI */ + +/* Sets of the above */ +#define INT_ALL (0x0003FFFE) /* All interrupts */ +#define INT_STATUS (0x0000003C) /* Some status pin changed */ +#define INT_DMASHUT (0x00038000) /* DMA engine got shut down */ +#define INT_SEGSHUT (0x00000700) /* Segmentation got shut down */ + +static inline u32 intr_pending(const struct lanai_dev *lanai) +{ + return reg_read(lanai, IntStatusMasked_Reg); +} + +static inline void intr_enable(const struct lanai_dev *lanai, u32 i) +{ + reg_write(lanai, i, IntControlEna_Reg); +} + +static inline void intr_disable(const struct lanai_dev *lanai, u32 i) +{ + reg_write(lanai, i, IntControlDis_Reg); +} + +/* -------------------- CARD/PCI STATUS: */ + +static void status_message(int itf, const char *name, int status) +{ + static const char *onoff[2] = { "off to on", "on to off" }; + printk(KERN_INFO DEV_LABEL "(itf %d): %s changed from %s\n", + itf, name, onoff[!status]); +} + +static void lanai_check_status(struct lanai_dev *lanai) +{ + u32 new = reg_read(lanai, Status_Reg); + u32 changes = new ^ lanai->status; + lanai->status = new; +#define e(flag, name) \ + if (changes & flag) \ + status_message(lanai->number, name, new & flag) + e(STATUS_SOOL, "SOOL"); + e(STATUS_LOCD, "LOCD"); + e(STATUS_LED, "LED"); + e(STATUS_GPIN, "GPIN"); +#undef e +} + +static void pcistatus_got(int itf, const char *name) +{ + printk(KERN_INFO DEV_LABEL "(itf %d): PCI got %s error\n", itf, name); +} + +static void pcistatus_check(struct lanai_dev *lanai, int clearonly) +{ + u16 s; + int result; + result = pci_read_config_word(lanai->pci, PCI_STATUS, &s); + if (result != PCIBIOS_SUCCESSFUL) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't read PCI_STATUS: " + "%d\n", lanai->number, result); + return; + } + s &= PCI_STATUS_DETECTED_PARITY | PCI_STATUS_SIG_SYSTEM_ERROR | + PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT | + PCI_STATUS_SIG_TARGET_ABORT | PCI_STATUS_PARITY; + if (s == 0) + return; + result = pci_write_config_word(lanai->pci, PCI_STATUS, s); + if (result != PCIBIOS_SUCCESSFUL) + printk(KERN_ERR DEV_LABEL "(itf %d): can't write PCI_STATUS: " + "%d\n", lanai->number, result); + if (clearonly) + return; +#define e(flag, name, stat) \ + if (s & flag) { \ + pcistatus_got(lanai->number, name); \ + ++lanai->stats.pcierr_##stat; \ + } + e(PCI_STATUS_DETECTED_PARITY, "parity", parity_detect); + e(PCI_STATUS_SIG_SYSTEM_ERROR, "signalled system", serr_set); + e(PCI_STATUS_REC_MASTER_ABORT, "master", master_abort); + e(PCI_STATUS_REC_TARGET_ABORT, "master target", m_target_abort); + e(PCI_STATUS_SIG_TARGET_ABORT, "slave", s_target_abort); + e(PCI_STATUS_PARITY, "master parity", master_parity); +#undef e +} + +/* -------------------- VCC TX BUFFER UTILITIES: */ + +/* space left in tx buffer in bytes */ +static inline int vcc_tx_space(const struct lanai_vcc *lvcc, int endptr) +{ + int r; + r = endptr * 16; + r -= ((unsigned long) lvcc->tx.buf.ptr) - + ((unsigned long) lvcc->tx.buf.start); + r -= 16; /* Leave "bubble" - if start==end it looks empty */ + if (r < 0) + r += lanai_buf_size(&lvcc->tx.buf); + return r; +} + +/* test if VCC is currently backlogged */ +static inline int vcc_is_backlogged(const struct lanai_vcc *lvcc) +{ + return !skb_queue_empty(&lvcc->tx.backlog); +} + +/* Bit fields in the segmentation buffer descriptor */ +#define DESCRIPTOR_MAGIC (0xD0000000) +#define DESCRIPTOR_AAL5 (0x00008000) +#define DESCRIPTOR_AAL5_STREAM (0x00004000) +#define DESCRIPTOR_CLP (0x00002000) + +/* Add 32-bit descriptor with its padding */ +static inline void vcc_tx_add_aal5_descriptor(struct lanai_vcc *lvcc, + u32 flags, int len) +{ + int pos; + APRINTK((((unsigned long) lvcc->tx.buf.ptr) & 15) == 0, + "vcc_tx_add_aal5_descriptor: bad ptr=%p\n", lvcc->tx.buf.ptr); + lvcc->tx.buf.ptr += 4; /* Hope the values REALLY don't matter */ + pos = ((unsigned char *) lvcc->tx.buf.ptr) - + (unsigned char *) lvcc->tx.buf.start; + APRINTK((pos & ~0x0001FFF0) == 0, + "vcc_tx_add_aal5_descriptor: bad pos (%d) before, vci=%d, " + "start,ptr,end=%p,%p,%p\n", pos, lvcc->vci, + lvcc->tx.buf.start, lvcc->tx.buf.ptr, lvcc->tx.buf.end); + pos = (pos + len) & (lanai_buf_size(&lvcc->tx.buf) - 1); + APRINTK((pos & ~0x0001FFF0) == 0, + "vcc_tx_add_aal5_descriptor: bad pos (%d) after, vci=%d, " + "start,ptr,end=%p,%p,%p\n", pos, lvcc->vci, + lvcc->tx.buf.start, lvcc->tx.buf.ptr, lvcc->tx.buf.end); + lvcc->tx.buf.ptr[-1] = + cpu_to_le32(DESCRIPTOR_MAGIC | DESCRIPTOR_AAL5 | + ((lvcc->tx.atmvcc->atm_options & ATM_ATMOPT_CLP) ? + DESCRIPTOR_CLP : 0) | flags | pos >> 4); + if (lvcc->tx.buf.ptr >= lvcc->tx.buf.end) + lvcc->tx.buf.ptr = lvcc->tx.buf.start; +} + +/* Add 32-bit AAL5 trailer and leave room for its CRC */ +static inline void vcc_tx_add_aal5_trailer(struct lanai_vcc *lvcc, + int len, int cpi, int uu) +{ + APRINTK((((unsigned long) lvcc->tx.buf.ptr) & 15) == 8, + "vcc_tx_add_aal5_trailer: bad ptr=%p\n", lvcc->tx.buf.ptr); + lvcc->tx.buf.ptr += 2; + lvcc->tx.buf.ptr[-2] = cpu_to_be32((uu << 24) | (cpi << 16) | len); + if (lvcc->tx.buf.ptr >= lvcc->tx.buf.end) + lvcc->tx.buf.ptr = lvcc->tx.buf.start; +} + +static inline void vcc_tx_memcpy(struct lanai_vcc *lvcc, + const unsigned char *src, int n) +{ + unsigned char *e; + int m; + e = ((unsigned char *) lvcc->tx.buf.ptr) + n; + m = e - (unsigned char *) lvcc->tx.buf.end; + if (m < 0) + m = 0; + memcpy(lvcc->tx.buf.ptr, src, n - m); + if (m != 0) { + memcpy(lvcc->tx.buf.start, src + n - m, m); + e = ((unsigned char *) lvcc->tx.buf.start) + m; + } + lvcc->tx.buf.ptr = (u32 *) e; +} + +static inline void vcc_tx_memzero(struct lanai_vcc *lvcc, int n) +{ + unsigned char *e; + int m; + if (n == 0) + return; + e = ((unsigned char *) lvcc->tx.buf.ptr) + n; + m = e - (unsigned char *) lvcc->tx.buf.end; + if (m < 0) + m = 0; + memset(lvcc->tx.buf.ptr, 0, n - m); + if (m != 0) { + memset(lvcc->tx.buf.start, 0, m); + e = ((unsigned char *) lvcc->tx.buf.start) + m; + } + lvcc->tx.buf.ptr = (u32 *) e; +} + +/* Update "butt" register to specify new WritePtr */ +static inline void lanai_endtx(struct lanai_dev *lanai, + const struct lanai_vcc *lvcc) +{ + int i, ptr = ((unsigned char *) lvcc->tx.buf.ptr) - + (unsigned char *) lvcc->tx.buf.start; + APRINTK((ptr & ~0x0001FFF0) == 0, + "lanai_endtx: bad ptr (%d), vci=%d, start,ptr,end=%p,%p,%p\n", + ptr, lvcc->vci, lvcc->tx.buf.start, lvcc->tx.buf.ptr, + lvcc->tx.buf.end); + + /* + * Since the "butt register" is a shared resounce on the card we + * serialize all accesses to it through this spinlock. This is + * mostly just paranoia since the register is rarely "busy" anyway + * but is needed for correctness. + */ + spin_lock(&lanai->endtxlock); + /* + * We need to check if the "butt busy" bit is set before + * updating the butt register. In theory this should + * never happen because the ATM card is plenty fast at + * updating the register. Still, we should make sure + */ + for (i = 0; reg_read(lanai, Status_Reg) & STATUS_BUTTBUSY; i++) { + if (unlikely(i > 50)) { + printk(KERN_ERR DEV_LABEL "(itf %d): butt register " + "always busy!\n", lanai->number); + break; + } + udelay(5); + } + /* + * Before we tall the card to start work we need to be sure 100% of + * the info in the service buffer has been written before we tell + * the card about it + */ + wmb(); + reg_write(lanai, (ptr << 12) | lvcc->vci, Butt_Reg); + spin_unlock(&lanai->endtxlock); +} + +/* + * Add one AAL5 PDU to lvcc's transmit buffer. Caller garauntees there's + * space available. "pdusize" is the number of bytes the PDU will take + */ +static void lanai_send_one_aal5(struct lanai_dev *lanai, + struct lanai_vcc *lvcc, struct sk_buff *skb, int pdusize) +{ + int pad; + APRINTK(pdusize == aal5_size(skb->len), + "lanai_send_one_aal5: wrong size packet (%d != %d)\n", + pdusize, aal5_size(skb->len)); + vcc_tx_add_aal5_descriptor(lvcc, 0, pdusize); + pad = pdusize - skb->len - 8; + APRINTK(pad >= 0, "pad is negative (%d)\n", pad); + APRINTK(pad < 48, "pad is too big (%d)\n", pad); + vcc_tx_memcpy(lvcc, skb->data, skb->len); + vcc_tx_memzero(lvcc, pad); + vcc_tx_add_aal5_trailer(lvcc, skb->len, 0, 0); + lanai_endtx(lanai, lvcc); + lanai_free_skb(lvcc->tx.atmvcc, skb); + atomic_inc(&lvcc->tx.atmvcc->stats->tx); +} + +/* Try to fill the buffer - don't call unless there is backlog */ +static void vcc_tx_unqueue_aal5(struct lanai_dev *lanai, + struct lanai_vcc *lvcc, int endptr) +{ + int n; + struct sk_buff *skb; + int space = vcc_tx_space(lvcc, endptr); + APRINTK(vcc_is_backlogged(lvcc), + "vcc_tx_unqueue() called with empty backlog (vci=%d)\n", + lvcc->vci); + while (space >= 64) { + skb = skb_dequeue(&lvcc->tx.backlog); + if (skb == NULL) + goto no_backlog; + n = aal5_size(skb->len); + if (n + 16 > space) { + /* No room for this packet - put it back on queue */ + skb_queue_head(&lvcc->tx.backlog, skb); + return; + } + lanai_send_one_aal5(lanai, lvcc, skb, n); + space -= n + 16; + } + if (!vcc_is_backlogged(lvcc)) { + no_backlog: + __clear_bit(lvcc->vci, lanai->backlog_vccs); + } +} + +/* Given an skb that we want to transmit either send it now or queue */ +static void vcc_tx_aal5(struct lanai_dev *lanai, struct lanai_vcc *lvcc, + struct sk_buff *skb) +{ + int space, n; + if (vcc_is_backlogged(lvcc)) /* Already backlogged */ + goto queue_it; + space = vcc_tx_space(lvcc, + TXREADPTR_GET_PTR(cardvcc_read(lvcc, vcc_txreadptr))); + n = aal5_size(skb->len); + APRINTK(n + 16 >= 64, "vcc_tx_aal5: n too small (%d)\n", n); + if (space < n + 16) { /* No space for this PDU */ + __set_bit(lvcc->vci, lanai->backlog_vccs); + queue_it: + skb_queue_tail(&lvcc->tx.backlog, skb); + return; + } + lanai_send_one_aal5(lanai, lvcc, skb, n); +} + +static void vcc_tx_unqueue_aal0(struct lanai_dev *lanai, + struct lanai_vcc *lvcc, int endptr) +{ + printk(KERN_INFO DEV_LABEL + ": vcc_tx_unqueue_aal0: not implemented\n"); +} + +static void vcc_tx_aal0(struct lanai_dev *lanai, struct lanai_vcc *lvcc, + struct sk_buff *skb) +{ + printk(KERN_INFO DEV_LABEL ": vcc_tx_aal0: not implemented\n"); + /* Remember to increment lvcc->tx.atmvcc->stats->tx */ + lanai_free_skb(lvcc->tx.atmvcc, skb); +} + +/* -------------------- VCC RX BUFFER UTILITIES: */ + +/* unlike the _tx_ cousins, this doesn't update ptr */ +static inline void vcc_rx_memcpy(unsigned char *dest, + const struct lanai_vcc *lvcc, int n) +{ + int m = ((const unsigned char *) lvcc->rx.buf.ptr) + n - + ((const unsigned char *) (lvcc->rx.buf.end)); + if (m < 0) + m = 0; + memcpy(dest, lvcc->rx.buf.ptr, n - m); + memcpy(dest + n - m, lvcc->rx.buf.start, m); + /* Make sure that these copies don't get reordered */ + barrier(); +} + +/* Receive AAL5 data on a VCC with a particular endptr */ +static void vcc_rx_aal5(struct lanai_vcc *lvcc, int endptr) +{ + int size; + struct sk_buff *skb; + const u32 *x; + u32 *end = &lvcc->rx.buf.start[endptr * 4]; + int n = ((unsigned long) end) - ((unsigned long) lvcc->rx.buf.ptr); + if (n < 0) + n += lanai_buf_size(&lvcc->rx.buf); + APRINTK(n >= 0 && n < lanai_buf_size(&lvcc->rx.buf) && !(n & 15), + "vcc_rx_aal5: n out of range (%d/%zu)\n", + n, lanai_buf_size(&lvcc->rx.buf)); + /* Recover the second-to-last word to get true pdu length */ + if ((x = &end[-2]) < lvcc->rx.buf.start) + x = &lvcc->rx.buf.end[-2]; + /* + * Before we actually read from the buffer, make sure the memory + * changes have arrived + */ + rmb(); + size = be32_to_cpup(x) & 0xffff; + if (unlikely(n != aal5_size(size))) { + /* Make sure size matches padding */ + printk(KERN_INFO DEV_LABEL "(itf %d): Got bad AAL5 length " + "on vci=%d - size=%d n=%d\n", + lvcc->rx.atmvcc->dev->number, lvcc->vci, size, n); + lvcc->stats.x.aal5.rx_badlen++; + goto out; + } + skb = atm_alloc_charge(lvcc->rx.atmvcc, size, GFP_ATOMIC); + if (unlikely(skb == NULL)) { + lvcc->stats.rx_nomem++; + goto out; + } + skb_put(skb, size); + vcc_rx_memcpy(skb->data, lvcc, size); + ATM_SKB(skb)->vcc = lvcc->rx.atmvcc; + __net_timestamp(skb); + lvcc->rx.atmvcc->push(lvcc->rx.atmvcc, skb); + atomic_inc(&lvcc->rx.atmvcc->stats->rx); + out: + lvcc->rx.buf.ptr = end; + cardvcc_write(lvcc, endptr, vcc_rxreadptr); +} + +static void vcc_rx_aal0(struct lanai_dev *lanai) +{ + printk(KERN_INFO DEV_LABEL ": vcc_rx_aal0: not implemented\n"); + /* Remember to get read_lock(&vcc_sklist_lock) while looking up VC */ + /* Remember to increment lvcc->rx.atmvcc->stats->rx */ +} + +/* -------------------- MANAGING HOST-BASED VCC TABLE: */ + +/* Decide whether to use vmalloc or get_zeroed_page for VCC table */ +#if (NUM_VCI * BITS_PER_LONG) <= PAGE_SIZE +#define VCCTABLE_GETFREEPAGE +#else +#include +#endif + +static int vcc_table_allocate(struct lanai_dev *lanai) +{ +#ifdef VCCTABLE_GETFREEPAGE + APRINTK((lanai->num_vci) * sizeof(struct lanai_vcc *) <= PAGE_SIZE, + "vcc table > PAGE_SIZE!"); + lanai->vccs = (struct lanai_vcc **) get_zeroed_page(GFP_KERNEL); + return (lanai->vccs == NULL) ? -ENOMEM : 0; +#else + int bytes = (lanai->num_vci) * sizeof(struct lanai_vcc *); + lanai->vccs = vzalloc(bytes); + if (unlikely(lanai->vccs == NULL)) + return -ENOMEM; + return 0; +#endif +} + +static inline void vcc_table_deallocate(const struct lanai_dev *lanai) +{ +#ifdef VCCTABLE_GETFREEPAGE + free_page((unsigned long) lanai->vccs); +#else + vfree(lanai->vccs); +#endif +} + +/* Allocate a fresh lanai_vcc, with the appropriate things cleared */ +static inline struct lanai_vcc *new_lanai_vcc(void) +{ + struct lanai_vcc *lvcc; + lvcc = kzalloc(sizeof(*lvcc), GFP_KERNEL); + if (likely(lvcc != NULL)) { + skb_queue_head_init(&lvcc->tx.backlog); +#ifdef DEBUG + lvcc->vci = -1; +#endif + } + return lvcc; +} + +static int lanai_get_sized_buffer(struct lanai_dev *lanai, + struct lanai_buffer *buf, int max_sdu, int multiplier, + const char *name) +{ + int size; + if (unlikely(max_sdu < 1)) + max_sdu = 1; + max_sdu = aal5_size(max_sdu); + size = (max_sdu + 16) * multiplier + 16; + lanai_buf_allocate(buf, size, max_sdu + 32, lanai->pci); + if (unlikely(buf->start == NULL)) + return -ENOMEM; + if (unlikely(lanai_buf_size(buf) < size)) + printk(KERN_WARNING DEV_LABEL "(itf %d): wanted %d bytes " + "for %s buffer, got only %zu\n", lanai->number, size, + name, lanai_buf_size(buf)); + DPRINTK("Allocated %zu byte %s buffer\n", lanai_buf_size(buf), name); + return 0; +} + +/* Setup a RX buffer for a currently unbound AAL5 vci */ +static inline int lanai_setup_rx_vci_aal5(struct lanai_dev *lanai, + struct lanai_vcc *lvcc, const struct atm_qos *qos) +{ + return lanai_get_sized_buffer(lanai, &lvcc->rx.buf, + qos->rxtp.max_sdu, AAL5_RX_MULTIPLIER, "RX"); +} + +/* Setup a TX buffer for a currently unbound AAL5 vci */ +static int lanai_setup_tx_vci(struct lanai_dev *lanai, struct lanai_vcc *lvcc, + const struct atm_qos *qos) +{ + int max_sdu, multiplier; + if (qos->aal == ATM_AAL0) { + lvcc->tx.unqueue = vcc_tx_unqueue_aal0; + max_sdu = ATM_CELL_SIZE - 1; + multiplier = AAL0_TX_MULTIPLIER; + } else { + lvcc->tx.unqueue = vcc_tx_unqueue_aal5; + max_sdu = qos->txtp.max_sdu; + multiplier = AAL5_TX_MULTIPLIER; + } + return lanai_get_sized_buffer(lanai, &lvcc->tx.buf, max_sdu, + multiplier, "TX"); +} + +static inline void host_vcc_bind(struct lanai_dev *lanai, + struct lanai_vcc *lvcc, vci_t vci) +{ + if (lvcc->vbase != NULL) + return; /* We already were bound in the other direction */ + DPRINTK("Binding vci %d\n", vci); +#ifdef USE_POWERDOWN + if (lanai->nbound++ == 0) { + DPRINTK("Coming out of powerdown\n"); + lanai->conf1 &= ~CONFIG1_POWERDOWN; + conf1_write(lanai); + conf2_write(lanai); + } +#endif + lvcc->vbase = cardvcc_addr(lanai, vci); + lanai->vccs[lvcc->vci = vci] = lvcc; +} + +static inline void host_vcc_unbind(struct lanai_dev *lanai, + struct lanai_vcc *lvcc) +{ + if (lvcc->vbase == NULL) + return; /* This vcc was never bound */ + DPRINTK("Unbinding vci %d\n", lvcc->vci); + lvcc->vbase = NULL; + lanai->vccs[lvcc->vci] = NULL; +#ifdef USE_POWERDOWN + if (--lanai->nbound == 0) { + DPRINTK("Going into powerdown\n"); + lanai->conf1 |= CONFIG1_POWERDOWN; + conf1_write(lanai); + } +#endif +} + +/* -------------------- RESET CARD: */ + +static void lanai_reset(struct lanai_dev *lanai) +{ + printk(KERN_CRIT DEV_LABEL "(itf %d): *NOT* resetting - not " + "implemented\n", lanai->number); + /* TODO */ + /* The following is just a hack until we write the real + * resetter - at least ack whatever interrupt sent us + * here + */ + reg_write(lanai, INT_ALL, IntAck_Reg); + lanai->stats.card_reset++; +} + +/* -------------------- SERVICE LIST UTILITIES: */ + +/* + * Allocate service buffer and tell card about it + */ +static int service_buffer_allocate(struct lanai_dev *lanai) +{ + lanai_buf_allocate(&lanai->service, SERVICE_ENTRIES * 4, 8, + lanai->pci); + if (unlikely(lanai->service.start == NULL)) + return -ENOMEM; + DPRINTK("allocated service buffer at %p, size %zu(%d)\n", + lanai->service.start, + lanai_buf_size(&lanai->service), + lanai_buf_size_cardorder(&lanai->service)); + /* Clear ServWrite register to be safe */ + reg_write(lanai, 0, ServWrite_Reg); + /* ServiceStuff register contains size and address of buffer */ + reg_write(lanai, + SSTUFF_SET_SIZE(lanai_buf_size_cardorder(&lanai->service)) | + SSTUFF_SET_ADDR(lanai->service.dmaaddr), + ServiceStuff_Reg); + return 0; +} + +static inline void service_buffer_deallocate(struct lanai_dev *lanai) +{ + lanai_buf_deallocate(&lanai->service, lanai->pci); +} + +/* Bitfields in service list */ +#define SERVICE_TX (0x80000000) /* Was from transmission */ +#define SERVICE_TRASH (0x40000000) /* RXed PDU was trashed */ +#define SERVICE_CRCERR (0x20000000) /* RXed PDU had CRC error */ +#define SERVICE_CI (0x10000000) /* RXed PDU had CI set */ +#define SERVICE_CLP (0x08000000) /* RXed PDU had CLP set */ +#define SERVICE_STREAM (0x04000000) /* RX Stream mode */ +#define SERVICE_GET_VCI(x) (((x)>>16)&0x3FF) +#define SERVICE_GET_END(x) ((x)&0x1FFF) + +/* Handle one thing from the service list - returns true if it marked a + * VCC ready for xmit + */ +static int handle_service(struct lanai_dev *lanai, u32 s) +{ + vci_t vci = SERVICE_GET_VCI(s); + struct lanai_vcc *lvcc; + read_lock(&vcc_sklist_lock); + lvcc = lanai->vccs[vci]; + if (unlikely(lvcc == NULL)) { + read_unlock(&vcc_sklist_lock); + DPRINTK("(itf %d) got service entry 0x%X for nonexistent " + "vcc %d\n", lanai->number, (unsigned int) s, vci); + if (s & SERVICE_TX) + lanai->stats.service_notx++; + else + lanai->stats.service_norx++; + return 0; + } + if (s & SERVICE_TX) { /* segmentation interrupt */ + if (unlikely(lvcc->tx.atmvcc == NULL)) { + read_unlock(&vcc_sklist_lock); + DPRINTK("(itf %d) got service entry 0x%X for non-TX " + "vcc %d\n", lanai->number, (unsigned int) s, vci); + lanai->stats.service_notx++; + return 0; + } + __set_bit(vci, lanai->transmit_ready); + lvcc->tx.endptr = SERVICE_GET_END(s); + read_unlock(&vcc_sklist_lock); + return 1; + } + if (unlikely(lvcc->rx.atmvcc == NULL)) { + read_unlock(&vcc_sklist_lock); + DPRINTK("(itf %d) got service entry 0x%X for non-RX " + "vcc %d\n", lanai->number, (unsigned int) s, vci); + lanai->stats.service_norx++; + return 0; + } + if (unlikely(lvcc->rx.atmvcc->qos.aal != ATM_AAL5)) { + read_unlock(&vcc_sklist_lock); + DPRINTK("(itf %d) got RX service entry 0x%X for non-AAL5 " + "vcc %d\n", lanai->number, (unsigned int) s, vci); + lanai->stats.service_rxnotaal5++; + atomic_inc(&lvcc->rx.atmvcc->stats->rx_err); + return 0; + } + if (likely(!(s & (SERVICE_TRASH | SERVICE_STREAM | SERVICE_CRCERR)))) { + vcc_rx_aal5(lvcc, SERVICE_GET_END(s)); + read_unlock(&vcc_sklist_lock); + return 0; + } + if (s & SERVICE_TRASH) { + int bytes; + read_unlock(&vcc_sklist_lock); + DPRINTK("got trashed rx pdu on vci %d\n", vci); + atomic_inc(&lvcc->rx.atmvcc->stats->rx_err); + lvcc->stats.x.aal5.service_trash++; + bytes = (SERVICE_GET_END(s) * 16) - + (((unsigned long) lvcc->rx.buf.ptr) - + ((unsigned long) lvcc->rx.buf.start)) + 47; + if (bytes < 0) + bytes += lanai_buf_size(&lvcc->rx.buf); + lanai->stats.ovfl_trash += (bytes / 48); + return 0; + } + if (s & SERVICE_STREAM) { + read_unlock(&vcc_sklist_lock); + atomic_inc(&lvcc->rx.atmvcc->stats->rx_err); + lvcc->stats.x.aal5.service_stream++; + printk(KERN_ERR DEV_LABEL "(itf %d): Got AAL5 stream " + "PDU on VCI %d!\n", lanai->number, vci); + lanai_reset(lanai); + return 0; + } + DPRINTK("got rx crc error on vci %d\n", vci); + atomic_inc(&lvcc->rx.atmvcc->stats->rx_err); + lvcc->stats.x.aal5.service_rxcrc++; + lvcc->rx.buf.ptr = &lvcc->rx.buf.start[SERVICE_GET_END(s) * 4]; + cardvcc_write(lvcc, SERVICE_GET_END(s), vcc_rxreadptr); + read_unlock(&vcc_sklist_lock); + return 0; +} + +/* Try transmitting on all VCIs that we marked ready to serve */ +static void iter_transmit(struct lanai_dev *lanai, vci_t vci) +{ + struct lanai_vcc *lvcc = lanai->vccs[vci]; + if (vcc_is_backlogged(lvcc)) + lvcc->tx.unqueue(lanai, lvcc, lvcc->tx.endptr); +} + +/* Run service queue -- called from interrupt context or with + * interrupts otherwise disabled and with the lanai->servicelock + * lock held + */ +static void run_service(struct lanai_dev *lanai) +{ + int ntx = 0; + u32 wreg = reg_read(lanai, ServWrite_Reg); + const u32 *end = lanai->service.start + wreg; + while (lanai->service.ptr != end) { + ntx += handle_service(lanai, + le32_to_cpup(lanai->service.ptr++)); + if (lanai->service.ptr >= lanai->service.end) + lanai->service.ptr = lanai->service.start; + } + reg_write(lanai, wreg, ServRead_Reg); + if (ntx != 0) { + read_lock(&vcc_sklist_lock); + vci_bitfield_iterate(lanai, lanai->transmit_ready, + iter_transmit); + bitmap_zero(lanai->transmit_ready, NUM_VCI); + read_unlock(&vcc_sklist_lock); + } +} + +/* -------------------- GATHER STATISTICS: */ + +static void get_statistics(struct lanai_dev *lanai) +{ + u32 statreg = reg_read(lanai, Statistics_Reg); + lanai->stats.atm_ovfl += STATS_GET_FIFO_OVFL(statreg); + lanai->stats.hec_err += STATS_GET_HEC_ERR(statreg); + lanai->stats.vci_trash += STATS_GET_BAD_VCI(statreg); + lanai->stats.ovfl_trash += STATS_GET_BUF_OVFL(statreg); +} + +/* -------------------- POLLING TIMER: */ + +#ifndef DEBUG_RW +/* Try to undequeue 1 backlogged vcc */ +static void iter_dequeue(struct lanai_dev *lanai, vci_t vci) +{ + struct lanai_vcc *lvcc = lanai->vccs[vci]; + int endptr; + if (lvcc == NULL || lvcc->tx.atmvcc == NULL || + !vcc_is_backlogged(lvcc)) { + __clear_bit(vci, lanai->backlog_vccs); + return; + } + endptr = TXREADPTR_GET_PTR(cardvcc_read(lvcc, vcc_txreadptr)); + lvcc->tx.unqueue(lanai, lvcc, endptr); +} +#endif /* !DEBUG_RW */ + +static void lanai_timed_poll(struct timer_list *t) +{ + struct lanai_dev *lanai = from_timer(lanai, t, timer); +#ifndef DEBUG_RW + unsigned long flags; +#ifdef USE_POWERDOWN + if (lanai->conf1 & CONFIG1_POWERDOWN) + return; +#endif /* USE_POWERDOWN */ + local_irq_save(flags); + /* If we can grab the spinlock, check if any services need to be run */ + if (spin_trylock(&lanai->servicelock)) { + run_service(lanai); + spin_unlock(&lanai->servicelock); + } + /* ...and see if any backlogged VCs can make progress */ + /* unfortunately linux has no read_trylock() currently */ + read_lock(&vcc_sklist_lock); + vci_bitfield_iterate(lanai, lanai->backlog_vccs, iter_dequeue); + read_unlock(&vcc_sklist_lock); + local_irq_restore(flags); + + get_statistics(lanai); +#endif /* !DEBUG_RW */ + mod_timer(&lanai->timer, jiffies + LANAI_POLL_PERIOD); +} + +static inline void lanai_timed_poll_start(struct lanai_dev *lanai) +{ + timer_setup(&lanai->timer, lanai_timed_poll, 0); + lanai->timer.expires = jiffies + LANAI_POLL_PERIOD; + add_timer(&lanai->timer); +} + +static inline void lanai_timed_poll_stop(struct lanai_dev *lanai) +{ + del_timer_sync(&lanai->timer); +} + +/* -------------------- INTERRUPT SERVICE: */ + +static inline void lanai_int_1(struct lanai_dev *lanai, u32 reason) +{ + u32 ack = 0; + if (reason & INT_SERVICE) { + ack = INT_SERVICE; + spin_lock(&lanai->servicelock); + run_service(lanai); + spin_unlock(&lanai->servicelock); + } + if (reason & (INT_AAL0_STR | INT_AAL0)) { + ack |= reason & (INT_AAL0_STR | INT_AAL0); + vcc_rx_aal0(lanai); + } + /* The rest of the interrupts are pretty rare */ + if (ack == reason) + goto done; + if (reason & INT_STATS) { + reason &= ~INT_STATS; /* No need to ack */ + get_statistics(lanai); + } + if (reason & INT_STATUS) { + ack |= reason & INT_STATUS; + lanai_check_status(lanai); + } + if (unlikely(reason & INT_DMASHUT)) { + printk(KERN_ERR DEV_LABEL "(itf %d): driver error - DMA " + "shutdown, reason=0x%08X, address=0x%08X\n", + lanai->number, (unsigned int) (reason & INT_DMASHUT), + (unsigned int) reg_read(lanai, DMA_Addr_Reg)); + if (reason & INT_TABORTBM) { + lanai_reset(lanai); + return; + } + ack |= (reason & INT_DMASHUT); + printk(KERN_ERR DEV_LABEL "(itf %d): re-enabling DMA\n", + lanai->number); + conf1_write(lanai); + lanai->stats.dma_reenable++; + pcistatus_check(lanai, 0); + } + if (unlikely(reason & INT_TABORTSENT)) { + ack |= (reason & INT_TABORTSENT); + printk(KERN_ERR DEV_LABEL "(itf %d): sent PCI target abort\n", + lanai->number); + pcistatus_check(lanai, 0); + } + if (unlikely(reason & INT_SEGSHUT)) { + printk(KERN_ERR DEV_LABEL "(itf %d): driver error - " + "segmentation shutdown, reason=0x%08X\n", lanai->number, + (unsigned int) (reason & INT_SEGSHUT)); + lanai_reset(lanai); + return; + } + if (unlikely(reason & (INT_PING | INT_WAKE))) { + printk(KERN_ERR DEV_LABEL "(itf %d): driver error - " + "unexpected interrupt 0x%08X, resetting\n", + lanai->number, + (unsigned int) (reason & (INT_PING | INT_WAKE))); + lanai_reset(lanai); + return; + } +#ifdef DEBUG + if (unlikely(ack != reason)) { + DPRINTK("unacked ints: 0x%08X\n", + (unsigned int) (reason & ~ack)); + ack = reason; + } +#endif + done: + if (ack != 0) + reg_write(lanai, ack, IntAck_Reg); +} + +static irqreturn_t lanai_int(int irq, void *devid) +{ + struct lanai_dev *lanai = devid; + u32 reason; + +#ifdef USE_POWERDOWN + /* + * If we're powered down we shouldn't be generating any interrupts - + * so assume that this is a shared interrupt line and it's for someone + * else + */ + if (unlikely(lanai->conf1 & CONFIG1_POWERDOWN)) + return IRQ_NONE; +#endif + + reason = intr_pending(lanai); + if (reason == 0) + return IRQ_NONE; /* Must be for someone else */ + + do { + if (unlikely(reason == 0xFFFFFFFF)) + break; /* Maybe we've been unplugged? */ + lanai_int_1(lanai, reason); + reason = intr_pending(lanai); + } while (reason != 0); + + return IRQ_HANDLED; +} + +/* TODO - it would be nice if we could use the "delayed interrupt" system + * to some advantage + */ + +/* -------------------- CHECK BOARD ID/REV: */ + +/* + * The board id and revision are stored both in the reset register and + * in the PCI configuration space - the documentation says to check + * each of them. If revp!=NULL we store the revision there + */ +static int check_board_id_and_rev(const char *name, u32 val, int *revp) +{ + DPRINTK("%s says board_id=%d, board_rev=%d\n", name, + (int) RESET_GET_BOARD_ID(val), + (int) RESET_GET_BOARD_REV(val)); + if (RESET_GET_BOARD_ID(val) != BOARD_ID_LANAI256) { + printk(KERN_ERR DEV_LABEL ": Found %s board-id %d -- not a " + "Lanai 25.6\n", name, (int) RESET_GET_BOARD_ID(val)); + return -ENODEV; + } + if (revp != NULL) + *revp = RESET_GET_BOARD_REV(val); + return 0; +} + +/* -------------------- PCI INITIALIZATION/SHUTDOWN: */ + +static int lanai_pci_start(struct lanai_dev *lanai) +{ + struct pci_dev *pci = lanai->pci; + int result; + + if (pci_enable_device(pci) != 0) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't enable " + "PCI device", lanai->number); + return -ENXIO; + } + pci_set_master(pci); + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32)) != 0) { + printk(KERN_WARNING DEV_LABEL + "(itf %d): No suitable DMA available.\n", lanai->number); + return -EBUSY; + } + result = check_board_id_and_rev("PCI", pci->subsystem_device, NULL); + if (result != 0) + return result; + /* Set latency timer to zero as per lanai docs */ + result = pci_write_config_byte(pci, PCI_LATENCY_TIMER, 0); + if (result != PCIBIOS_SUCCESSFUL) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't write " + "PCI_LATENCY_TIMER: %d\n", lanai->number, result); + return -EINVAL; + } + pcistatus_check(lanai, 1); + pcistatus_check(lanai, 0); + return 0; +} + +/* -------------------- VPI/VCI ALLOCATION: */ + +/* + * We _can_ use VCI==0 for normal traffic, but only for UBR (or we'll + * get a CBRZERO interrupt), and we can use it only if no one is receiving + * AAL0 traffic (since they will use the same queue) - according to the + * docs we shouldn't even use it for AAL0 traffic + */ +static inline int vci0_is_ok(struct lanai_dev *lanai, + const struct atm_qos *qos) +{ + if (qos->txtp.traffic_class == ATM_CBR || qos->aal == ATM_AAL0) + return 0; + if (qos->rxtp.traffic_class != ATM_NONE) { + if (lanai->naal0 != 0) + return 0; + lanai->conf2 |= CONFIG2_VCI0_NORMAL; + conf2_write_if_powerup(lanai); + } + return 1; +} + +/* return true if vci is currently unused, or if requested qos is + * compatible + */ +static int vci_is_ok(struct lanai_dev *lanai, vci_t vci, + const struct atm_vcc *atmvcc) +{ + const struct atm_qos *qos = &atmvcc->qos; + const struct lanai_vcc *lvcc = lanai->vccs[vci]; + if (vci == 0 && !vci0_is_ok(lanai, qos)) + return 0; + if (unlikely(lvcc != NULL)) { + if (qos->rxtp.traffic_class != ATM_NONE && + lvcc->rx.atmvcc != NULL && lvcc->rx.atmvcc != atmvcc) + return 0; + if (qos->txtp.traffic_class != ATM_NONE && + lvcc->tx.atmvcc != NULL && lvcc->tx.atmvcc != atmvcc) + return 0; + if (qos->txtp.traffic_class == ATM_CBR && + lanai->cbrvcc != NULL && lanai->cbrvcc != atmvcc) + return 0; + } + if (qos->aal == ATM_AAL0 && lanai->naal0 == 0 && + qos->rxtp.traffic_class != ATM_NONE) { + const struct lanai_vcc *vci0 = lanai->vccs[0]; + if (vci0 != NULL && vci0->rx.atmvcc != NULL) + return 0; + lanai->conf2 &= ~CONFIG2_VCI0_NORMAL; + conf2_write_if_powerup(lanai); + } + return 1; +} + +static int lanai_normalize_ci(struct lanai_dev *lanai, + const struct atm_vcc *atmvcc, short *vpip, vci_t *vcip) +{ + switch (*vpip) { + case ATM_VPI_ANY: + *vpip = 0; + fallthrough; + case 0: + break; + default: + return -EADDRINUSE; + } + switch (*vcip) { + case ATM_VCI_ANY: + for (*vcip = ATM_NOT_RSV_VCI; *vcip < lanai->num_vci; + (*vcip)++) + if (vci_is_ok(lanai, *vcip, atmvcc)) + return 0; + return -EADDRINUSE; + default: + if (*vcip >= lanai->num_vci || *vcip < 0 || + !vci_is_ok(lanai, *vcip, atmvcc)) + return -EADDRINUSE; + } + return 0; +} + +/* -------------------- MANAGE CBR: */ + +/* + * CBR ICG is stored as a fixed-point number with 4 fractional bits. + * Note that storing a number greater than 2046.0 will result in + * incorrect shaping + */ +#define CBRICG_FRAC_BITS (4) +#define CBRICG_MAX (2046 << CBRICG_FRAC_BITS) + +/* + * ICG is related to PCR with the formula PCR = MAXPCR / (ICG + 1) + * where MAXPCR is (according to the docs) 25600000/(54*8), + * which is equal to (3125<<9)/27. + * + * Solving for ICG, we get: + * ICG = MAXPCR/PCR - 1 + * ICG = (3125<<9)/(27*PCR) - 1 + * ICG = ((3125<<9) - (27*PCR)) / (27*PCR) + * + * The end result is supposed to be a fixed-point number with FRAC_BITS + * bits of a fractional part, so we keep everything in the numerator + * shifted by that much as we compute + * + */ +static int pcr_to_cbricg(const struct atm_qos *qos) +{ + int rounddown = 0; /* 1 = Round PCR down, i.e. round ICG _up_ */ + int x, icg, pcr = atm_pcr_goal(&qos->txtp); + if (pcr == 0) /* Use maximum bandwidth */ + return 0; + if (pcr < 0) { + rounddown = 1; + pcr = -pcr; + } + x = pcr * 27; + icg = (3125 << (9 + CBRICG_FRAC_BITS)) - (x << CBRICG_FRAC_BITS); + if (rounddown) + icg += x - 1; + icg /= x; + if (icg > CBRICG_MAX) + icg = CBRICG_MAX; + DPRINTK("pcr_to_cbricg: pcr=%d rounddown=%c icg=%d\n", + pcr, rounddown ? 'Y' : 'N', icg); + return icg; +} + +static inline void lanai_cbr_setup(struct lanai_dev *lanai) +{ + reg_write(lanai, pcr_to_cbricg(&lanai->cbrvcc->qos), CBR_ICG_Reg); + reg_write(lanai, lanai->cbrvcc->vci, CBR_PTR_Reg); + lanai->conf2 |= CONFIG2_CBR_ENABLE; + conf2_write(lanai); +} + +static inline void lanai_cbr_shutdown(struct lanai_dev *lanai) +{ + lanai->conf2 &= ~CONFIG2_CBR_ENABLE; + conf2_write(lanai); +} + +/* -------------------- OPERATIONS: */ + +/* setup a newly detected device */ +static int lanai_dev_open(struct atm_dev *atmdev) +{ + struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data; + unsigned long raw_base; + int result; + + DPRINTK("In lanai_dev_open()\n"); + /* Basic device fields */ + lanai->number = atmdev->number; + lanai->num_vci = NUM_VCI; + bitmap_zero(lanai->backlog_vccs, NUM_VCI); + bitmap_zero(lanai->transmit_ready, NUM_VCI); + lanai->naal0 = 0; +#ifdef USE_POWERDOWN + lanai->nbound = 0; +#endif + lanai->cbrvcc = NULL; + memset(&lanai->stats, 0, sizeof lanai->stats); + spin_lock_init(&lanai->endtxlock); + spin_lock_init(&lanai->servicelock); + atmdev->ci_range.vpi_bits = 0; + atmdev->ci_range.vci_bits = 0; + while (1 << atmdev->ci_range.vci_bits < lanai->num_vci) + atmdev->ci_range.vci_bits++; + atmdev->link_rate = ATM_25_PCR; + + /* 3.2: PCI initialization */ + if ((result = lanai_pci_start(lanai)) != 0) + goto error; + raw_base = lanai->pci->resource[0].start; + lanai->base = (bus_addr_t) ioremap(raw_base, LANAI_MAPPING_SIZE); + if (lanai->base == NULL) { + printk(KERN_ERR DEV_LABEL ": couldn't remap I/O space\n"); + result = -ENOMEM; + goto error_pci; + } + /* 3.3: Reset lanai and PHY */ + reset_board(lanai); + lanai->conf1 = reg_read(lanai, Config1_Reg); + lanai->conf1 &= ~(CONFIG1_GPOUT1 | CONFIG1_POWERDOWN | + CONFIG1_MASK_LEDMODE); + lanai->conf1 |= CONFIG1_SET_LEDMODE(LEDMODE_NOT_SOOL); + reg_write(lanai, lanai->conf1 | CONFIG1_GPOUT1, Config1_Reg); + udelay(1000); + conf1_write(lanai); + + /* + * 3.4: Turn on endian mode for big-endian hardware + * We don't actually want to do this - the actual bit fields + * in the endian register are not documented anywhere. + * Instead we do the bit-flipping ourselves on big-endian + * hardware. + * + * 3.5: get the board ID/rev by reading the reset register + */ + result = check_board_id_and_rev("register", + reg_read(lanai, Reset_Reg), &lanai->board_rev); + if (result != 0) + goto error_unmap; + + /* 3.6: read EEPROM */ + if ((result = eeprom_read(lanai)) != 0) + goto error_unmap; + if ((result = eeprom_validate(lanai)) != 0) + goto error_unmap; + + /* 3.7: re-reset PHY, do loopback tests, setup PHY */ + reg_write(lanai, lanai->conf1 | CONFIG1_GPOUT1, Config1_Reg); + udelay(1000); + conf1_write(lanai); + /* TODO - loopback tests */ + lanai->conf1 |= (CONFIG1_GPOUT2 | CONFIG1_GPOUT3 | CONFIG1_DMA_ENABLE); + conf1_write(lanai); + + /* 3.8/3.9: test and initialize card SRAM */ + if ((result = sram_test_and_clear(lanai)) != 0) + goto error_unmap; + + /* 3.10: initialize lanai registers */ + lanai->conf1 |= CONFIG1_DMA_ENABLE; + conf1_write(lanai); + if ((result = service_buffer_allocate(lanai)) != 0) + goto error_unmap; + if ((result = vcc_table_allocate(lanai)) != 0) + goto error_service; + lanai->conf2 = (lanai->num_vci >= 512 ? CONFIG2_HOWMANY : 0) | + CONFIG2_HEC_DROP | /* ??? */ CONFIG2_PTI7_MODE; + conf2_write(lanai); + reg_write(lanai, TX_FIFO_DEPTH, TxDepth_Reg); + reg_write(lanai, 0, CBR_ICG_Reg); /* CBR defaults to no limit */ + if ((result = request_irq(lanai->pci->irq, lanai_int, IRQF_SHARED, + DEV_LABEL, lanai)) != 0) { + printk(KERN_ERR DEV_LABEL ": can't allocate interrupt\n"); + goto error_vcctable; + } + mb(); /* Make sure that all that made it */ + intr_enable(lanai, INT_ALL & ~(INT_PING | INT_WAKE)); + /* 3.11: initialize loop mode (i.e. turn looping off) */ + lanai->conf1 = (lanai->conf1 & ~CONFIG1_MASK_LOOPMODE) | + CONFIG1_SET_LOOPMODE(LOOPMODE_NORMAL) | + CONFIG1_GPOUT2 | CONFIG1_GPOUT3; + conf1_write(lanai); + lanai->status = reg_read(lanai, Status_Reg); + /* We're now done initializing this card */ +#ifdef USE_POWERDOWN + lanai->conf1 |= CONFIG1_POWERDOWN; + conf1_write(lanai); +#endif + memcpy(atmdev->esi, eeprom_mac(lanai), ESI_LEN); + lanai_timed_poll_start(lanai); + printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d, base=%p, irq=%u " + "(%pMF)\n", lanai->number, (int) lanai->pci->revision, + lanai->base, lanai->pci->irq, atmdev->esi); + printk(KERN_NOTICE DEV_LABEL "(itf %d): LANAI%s, serialno=%u(0x%X), " + "board_rev=%d\n", lanai->number, + lanai->type==lanai2 ? "2" : "HB", (unsigned int) lanai->serialno, + (unsigned int) lanai->serialno, lanai->board_rev); + return 0; + + error_vcctable: + vcc_table_deallocate(lanai); + error_service: + service_buffer_deallocate(lanai); + error_unmap: + reset_board(lanai); +#ifdef USE_POWERDOWN + lanai->conf1 = reg_read(lanai, Config1_Reg) | CONFIG1_POWERDOWN; + conf1_write(lanai); +#endif + iounmap(lanai->base); + lanai->base = NULL; + error_pci: + pci_disable_device(lanai->pci); + error: + return result; +} + +/* called when device is being shutdown, and all vcc's are gone - higher + * levels will deallocate the atm device for us + */ +static void lanai_dev_close(struct atm_dev *atmdev) +{ + struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data; + if (lanai->base==NULL) + return; + printk(KERN_INFO DEV_LABEL "(itf %d): shutting down interface\n", + lanai->number); + lanai_timed_poll_stop(lanai); +#ifdef USE_POWERDOWN + lanai->conf1 = reg_read(lanai, Config1_Reg) & ~CONFIG1_POWERDOWN; + conf1_write(lanai); +#endif + intr_disable(lanai, INT_ALL); + free_irq(lanai->pci->irq, lanai); + reset_board(lanai); +#ifdef USE_POWERDOWN + lanai->conf1 |= CONFIG1_POWERDOWN; + conf1_write(lanai); +#endif + pci_disable_device(lanai->pci); + vcc_table_deallocate(lanai); + service_buffer_deallocate(lanai); + iounmap(lanai->base); + kfree(lanai); +} + +/* close a vcc */ +static void lanai_close(struct atm_vcc *atmvcc) +{ + struct lanai_vcc *lvcc = (struct lanai_vcc *) atmvcc->dev_data; + struct lanai_dev *lanai = (struct lanai_dev *) atmvcc->dev->dev_data; + if (lvcc == NULL) + return; + clear_bit(ATM_VF_READY, &atmvcc->flags); + clear_bit(ATM_VF_PARTIAL, &atmvcc->flags); + if (lvcc->rx.atmvcc == atmvcc) { + lanai_shutdown_rx_vci(lvcc); + if (atmvcc->qos.aal == ATM_AAL0) { + if (--lanai->naal0 <= 0) + aal0_buffer_free(lanai); + } else + lanai_buf_deallocate(&lvcc->rx.buf, lanai->pci); + lvcc->rx.atmvcc = NULL; + } + if (lvcc->tx.atmvcc == atmvcc) { + if (atmvcc == lanai->cbrvcc) { + if (lvcc->vbase != NULL) + lanai_cbr_shutdown(lanai); + lanai->cbrvcc = NULL; + } + lanai_shutdown_tx_vci(lanai, lvcc); + lanai_buf_deallocate(&lvcc->tx.buf, lanai->pci); + lvcc->tx.atmvcc = NULL; + } + if (--lvcc->nref == 0) { + host_vcc_unbind(lanai, lvcc); + kfree(lvcc); + } + atmvcc->dev_data = NULL; + clear_bit(ATM_VF_ADDR, &atmvcc->flags); +} + +/* open a vcc on the card to vpi/vci */ +static int lanai_open(struct atm_vcc *atmvcc) +{ + struct lanai_dev *lanai; + struct lanai_vcc *lvcc; + int result = 0; + int vci = atmvcc->vci; + short vpi = atmvcc->vpi; + /* we don't support partial open - it's not really useful anyway */ + if ((test_bit(ATM_VF_PARTIAL, &atmvcc->flags)) || + (vpi == ATM_VPI_UNSPEC) || (vci == ATM_VCI_UNSPEC)) + return -EINVAL; + lanai = (struct lanai_dev *) atmvcc->dev->dev_data; + result = lanai_normalize_ci(lanai, atmvcc, &vpi, &vci); + if (unlikely(result != 0)) + goto out; + set_bit(ATM_VF_ADDR, &atmvcc->flags); + if (atmvcc->qos.aal != ATM_AAL0 && atmvcc->qos.aal != ATM_AAL5) + return -EINVAL; + DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n", lanai->number, + (int) vpi, vci); + lvcc = lanai->vccs[vci]; + if (lvcc == NULL) { + lvcc = new_lanai_vcc(); + if (unlikely(lvcc == NULL)) + return -ENOMEM; + atmvcc->dev_data = lvcc; + } + lvcc->nref++; + if (atmvcc->qos.rxtp.traffic_class != ATM_NONE) { + APRINTK(lvcc->rx.atmvcc == NULL, "rx.atmvcc!=NULL, vci=%d\n", + vci); + if (atmvcc->qos.aal == ATM_AAL0) { + if (lanai->naal0 == 0) + result = aal0_buffer_allocate(lanai); + } else + result = lanai_setup_rx_vci_aal5( + lanai, lvcc, &atmvcc->qos); + if (unlikely(result != 0)) + goto out_free; + lvcc->rx.atmvcc = atmvcc; + lvcc->stats.rx_nomem = 0; + lvcc->stats.x.aal5.rx_badlen = 0; + lvcc->stats.x.aal5.service_trash = 0; + lvcc->stats.x.aal5.service_stream = 0; + lvcc->stats.x.aal5.service_rxcrc = 0; + if (atmvcc->qos.aal == ATM_AAL0) + lanai->naal0++; + } + if (atmvcc->qos.txtp.traffic_class != ATM_NONE) { + APRINTK(lvcc->tx.atmvcc == NULL, "tx.atmvcc!=NULL, vci=%d\n", + vci); + result = lanai_setup_tx_vci(lanai, lvcc, &atmvcc->qos); + if (unlikely(result != 0)) + goto out_free; + lvcc->tx.atmvcc = atmvcc; + if (atmvcc->qos.txtp.traffic_class == ATM_CBR) { + APRINTK(lanai->cbrvcc == NULL, + "cbrvcc!=NULL, vci=%d\n", vci); + lanai->cbrvcc = atmvcc; + } + } + host_vcc_bind(lanai, lvcc, vci); + /* + * Make sure everything made it to RAM before we tell the card about + * the VCC + */ + wmb(); + if (atmvcc == lvcc->rx.atmvcc) + host_vcc_start_rx(lvcc); + if (atmvcc == lvcc->tx.atmvcc) { + host_vcc_start_tx(lvcc); + if (lanai->cbrvcc == atmvcc) + lanai_cbr_setup(lanai); + } + set_bit(ATM_VF_READY, &atmvcc->flags); + return 0; + out_free: + lanai_close(atmvcc); + out: + return result; +} + +static int lanai_send(struct atm_vcc *atmvcc, struct sk_buff *skb) +{ + struct lanai_vcc *lvcc = (struct lanai_vcc *) atmvcc->dev_data; + struct lanai_dev *lanai = (struct lanai_dev *) atmvcc->dev->dev_data; + unsigned long flags; + if (unlikely(lvcc == NULL || lvcc->vbase == NULL || + lvcc->tx.atmvcc != atmvcc)) + goto einval; +#ifdef DEBUG + if (unlikely(skb == NULL)) { + DPRINTK("lanai_send: skb==NULL for vci=%d\n", atmvcc->vci); + goto einval; + } + if (unlikely(lanai == NULL)) { + DPRINTK("lanai_send: lanai==NULL for vci=%d\n", atmvcc->vci); + goto einval; + } +#endif + ATM_SKB(skb)->vcc = atmvcc; + switch (atmvcc->qos.aal) { + case ATM_AAL5: + read_lock_irqsave(&vcc_sklist_lock, flags); + vcc_tx_aal5(lanai, lvcc, skb); + read_unlock_irqrestore(&vcc_sklist_lock, flags); + return 0; + case ATM_AAL0: + if (unlikely(skb->len != ATM_CELL_SIZE-1)) + goto einval; + /* NOTE - this next line is technically invalid - we haven't unshared skb */ + cpu_to_be32s((u32 *) skb->data); + read_lock_irqsave(&vcc_sklist_lock, flags); + vcc_tx_aal0(lanai, lvcc, skb); + read_unlock_irqrestore(&vcc_sklist_lock, flags); + return 0; + } + DPRINTK("lanai_send: bad aal=%d on vci=%d\n", (int) atmvcc->qos.aal, + atmvcc->vci); + einval: + lanai_free_skb(atmvcc, skb); + return -EINVAL; +} + +static int lanai_change_qos(struct atm_vcc *atmvcc, + /*const*/ struct atm_qos *qos, int flags) +{ + return -EBUSY; /* TODO: need to write this */ +} + +#ifndef CONFIG_PROC_FS +#define lanai_proc_read NULL +#else +static int lanai_proc_read(struct atm_dev *atmdev, loff_t *pos, char *page) +{ + struct lanai_dev *lanai = (struct lanai_dev *) atmdev->dev_data; + loff_t left = *pos; + struct lanai_vcc *lvcc; + if (left-- == 0) + return sprintf(page, DEV_LABEL "(itf %d): chip=LANAI%s, " + "serial=%u, magic=0x%08X, num_vci=%d\n", + atmdev->number, lanai->type==lanai2 ? "2" : "HB", + (unsigned int) lanai->serialno, + (unsigned int) lanai->magicno, lanai->num_vci); + if (left-- == 0) + return sprintf(page, "revision: board=%d, pci_if=%d\n", + lanai->board_rev, (int) lanai->pci->revision); + if (left-- == 0) + return sprintf(page, "EEPROM ESI: %pM\n", + &lanai->eeprom[EEPROM_MAC]); + if (left-- == 0) + return sprintf(page, "status: SOOL=%d, LOCD=%d, LED=%d, " + "GPIN=%d\n", (lanai->status & STATUS_SOOL) ? 1 : 0, + (lanai->status & STATUS_LOCD) ? 1 : 0, + (lanai->status & STATUS_LED) ? 1 : 0, + (lanai->status & STATUS_GPIN) ? 1 : 0); + if (left-- == 0) + return sprintf(page, "global buffer sizes: service=%zu, " + "aal0_rx=%zu\n", lanai_buf_size(&lanai->service), + lanai->naal0 ? lanai_buf_size(&lanai->aal0buf) : 0); + if (left-- == 0) { + get_statistics(lanai); + return sprintf(page, "cells in error: overflow=%u, " + "closed_vci=%u, bad_HEC=%u, rx_fifo=%u\n", + lanai->stats.ovfl_trash, lanai->stats.vci_trash, + lanai->stats.hec_err, lanai->stats.atm_ovfl); + } + if (left-- == 0) + return sprintf(page, "PCI errors: parity_detect=%u, " + "master_abort=%u, master_target_abort=%u,\n", + lanai->stats.pcierr_parity_detect, + lanai->stats.pcierr_serr_set, + lanai->stats.pcierr_m_target_abort); + if (left-- == 0) + return sprintf(page, " slave_target_abort=%u, " + "master_parity=%u\n", lanai->stats.pcierr_s_target_abort, + lanai->stats.pcierr_master_parity); + if (left-- == 0) + return sprintf(page, " no_tx=%u, " + "no_rx=%u, bad_rx_aal=%u\n", lanai->stats.service_norx, + lanai->stats.service_notx, + lanai->stats.service_rxnotaal5); + if (left-- == 0) + return sprintf(page, "resets: dma=%u, card=%u\n", + lanai->stats.dma_reenable, lanai->stats.card_reset); + /* At this point, "left" should be the VCI we're looking for */ + read_lock(&vcc_sklist_lock); + for (; ; left++) { + if (left >= NUM_VCI) { + left = 0; + goto out; + } + if ((lvcc = lanai->vccs[left]) != NULL) + break; + (*pos)++; + } + /* Note that we re-use "left" here since we're done with it */ + left = sprintf(page, "VCI %4d: nref=%d, rx_nomem=%u", (vci_t) left, + lvcc->nref, lvcc->stats.rx_nomem); + if (lvcc->rx.atmvcc != NULL) { + left += sprintf(&page[left], ",\n rx_AAL=%d", + lvcc->rx.atmvcc->qos.aal == ATM_AAL5 ? 5 : 0); + if (lvcc->rx.atmvcc->qos.aal == ATM_AAL5) + left += sprintf(&page[left], ", rx_buf_size=%zu, " + "rx_bad_len=%u,\n rx_service_trash=%u, " + "rx_service_stream=%u, rx_bad_crc=%u", + lanai_buf_size(&lvcc->rx.buf), + lvcc->stats.x.aal5.rx_badlen, + lvcc->stats.x.aal5.service_trash, + lvcc->stats.x.aal5.service_stream, + lvcc->stats.x.aal5.service_rxcrc); + } + if (lvcc->tx.atmvcc != NULL) + left += sprintf(&page[left], ",\n tx_AAL=%d, " + "tx_buf_size=%zu, tx_qos=%cBR, tx_backlogged=%c", + lvcc->tx.atmvcc->qos.aal == ATM_AAL5 ? 5 : 0, + lanai_buf_size(&lvcc->tx.buf), + lvcc->tx.atmvcc == lanai->cbrvcc ? 'C' : 'U', + vcc_is_backlogged(lvcc) ? 'Y' : 'N'); + page[left++] = '\n'; + page[left] = '\0'; + out: + read_unlock(&vcc_sklist_lock); + return left; +} +#endif /* CONFIG_PROC_FS */ + +/* -------------------- HOOKS: */ + +static const struct atmdev_ops ops = { + .dev_close = lanai_dev_close, + .open = lanai_open, + .close = lanai_close, + .send = lanai_send, + .phy_put = NULL, + .phy_get = NULL, + .change_qos = lanai_change_qos, + .proc_read = lanai_proc_read, + .owner = THIS_MODULE +}; + +/* initialize one probed card */ +static int lanai_init_one(struct pci_dev *pci, + const struct pci_device_id *ident) +{ + struct lanai_dev *lanai; + struct atm_dev *atmdev; + int result; + + lanai = kzalloc(sizeof(*lanai), GFP_KERNEL); + if (lanai == NULL) { + printk(KERN_ERR DEV_LABEL + ": couldn't allocate dev_data structure!\n"); + return -ENOMEM; + } + + atmdev = atm_dev_register(DEV_LABEL, &pci->dev, &ops, -1, NULL); + if (atmdev == NULL) { + printk(KERN_ERR DEV_LABEL + ": couldn't register atm device!\n"); + kfree(lanai); + return -EBUSY; + } + + atmdev->dev_data = lanai; + lanai->pci = pci; + lanai->type = (enum lanai_type) ident->device; + + result = lanai_dev_open(atmdev); + if (result != 0) { + DPRINTK("lanai_start() failed, err=%d\n", -result); + atm_dev_deregister(atmdev); + kfree(lanai); + } + return result; +} + +static const struct pci_device_id lanai_pci_tbl[] = { + { PCI_VDEVICE(EF, PCI_DEVICE_ID_EF_ATM_LANAI2) }, + { PCI_VDEVICE(EF, PCI_DEVICE_ID_EF_ATM_LANAIHB) }, + { 0, } /* terminal entry */ +}; +MODULE_DEVICE_TABLE(pci, lanai_pci_tbl); + +static struct pci_driver lanai_driver = { + .name = DEV_LABEL, + .id_table = lanai_pci_tbl, + .probe = lanai_init_one, +}; + +module_pci_driver(lanai_driver); + +MODULE_AUTHOR("Mitchell Blank Jr "); +MODULE_DESCRIPTION("Efficient Networks Speedstream 3010 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/atm/midway.h b/drivers/atm/midway.h new file mode 100644 index 000000000..d47307adc --- /dev/null +++ b/drivers/atm/midway.h @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/midway.h - Efficient Networks Midway (SAR) description */ + +/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */ + + +#ifndef DRIVERS_ATM_MIDWAY_H +#define DRIVERS_ATM_MIDWAY_H + + +#define NR_VCI 1024 /* number of VCIs */ +#define NR_VCI_LD 10 /* log2(NR_VCI) */ +#define NR_DMA_RX 512 /* RX DMA queue entries */ +#define NR_DMA_TX 512 /* TX DMA queue entries */ +#define NR_SERVICE NR_VCI /* service list size */ +#define NR_CHAN 8 /* number of TX channels */ +#define TS_CLOCK 25000000 /* traffic shaper clock (cell/sec) */ + +#define MAP_MAX_SIZE 0x00400000 /* memory window for max config */ +#define EPROM_SIZE 0x00010000 +#define MEM_VALID 0xffc00000 /* mask base address with this */ +#define PHY_BASE 0x00020000 /* offset of PHY register are */ +#define REG_BASE 0x00040000 /* offset of Midway register area */ +#define RAM_BASE 0x00200000 /* offset of RAM area */ +#define RAM_INCREMENT 0x00020000 /* probe for RAM every 128kB */ + +#define MID_VCI_BASE RAM_BASE +#define MID_DMA_RX_BASE (MID_VCI_BASE+NR_VCI*16) +#define MID_DMA_TX_BASE (MID_DMA_RX_BASE+NR_DMA_RX*8) +#define MID_SERVICE_BASE (MID_DMA_TX_BASE+NR_DMA_TX*8) +#define MID_FREE_BASE (MID_SERVICE_BASE+NR_SERVICE*4) + +#define MAC_LEN 6 /* atm.h */ + +#define MID_MIN_BUF_SIZE (1024) /* 1 kB is minimum */ +#define MID_MAX_BUF_SIZE (128*1024) /* 128 kB is maximum */ + +#define RX_DESCR_SIZE 1 /* RX PDU descr is 1 longword */ +#define TX_DESCR_SIZE 2 /* TX PDU descr is 2 longwords */ +#define AAL5_TRAILER (ATM_AAL5_TRAILER/4) /* AAL5 trailer is 2 longwords */ + +#define TX_GAP 8 /* TX buffer gap (words) */ + +/* + * Midway Reset/ID + * + * All values read-only. Writing to this register resets Midway chip. + */ + +#define MID_RES_ID_MCON 0x00 /* Midway Reset/ID */ + +#define MID_ID 0xf0000000 /* Midway version */ +#define MID_SHIFT 24 +#define MID_MOTHER_ID 0x00000700 /* mother board id */ +#define MID_MOTHER_SHIFT 8 +#define MID_CON_TI 0x00000080 /* 0: normal ctrl; 1: SABRE */ +#define MID_CON_SUNI 0x00000040 /* 0: UTOPIA; 1: SUNI */ +#define MID_CON_V6 0x00000020 /* 0: non-pipel UTOPIA (required iff + !CON_SUNI; 1: UTOPIA */ +#define DAUGHTER_ID 0x0000001f /* daughter board id */ + +/* + * Interrupt Status Acknowledge, Interrupt Status & Interrupt Enable + */ + +#define MID_ISA 0x01 /* Interrupt Status Acknowledge */ +#define MID_IS 0x02 /* Interrupt Status */ +#define MID_IE 0x03 /* Interrupt Enable */ + +#define MID_TX_COMPLETE_7 0x00010000 /* channel N completed a PDU */ +#define MID_TX_COMPLETE_6 0x00008000 /* transmission */ +#define MID_TX_COMPLETE_5 0x00004000 +#define MID_TX_COMPLETE_4 0x00002000 +#define MID_TX_COMPLETE_3 0x00001000 +#define MID_TX_COMPLETE_2 0x00000800 +#define MID_TX_COMPLETE_1 0x00000400 +#define MID_TX_COMPLETE_0 0x00000200 +#define MID_TX_COMPLETE 0x0001fe00 /* any TX */ +#define MID_TX_DMA_OVFL 0x00000100 /* DMA to adapter overflow */ +#define MID_TX_IDENT_MISM 0x00000080 /* TX: ident mismatch => halted */ +#define MID_DMA_LERR_ACK 0x00000040 /* LERR - SBus ? */ +#define MID_DMA_ERR_ACK 0x00000020 /* DMA error */ +#define MID_RX_DMA_COMPLETE 0x00000010 /* DMA to host done */ +#define MID_TX_DMA_COMPLETE 0x00000008 /* DMA from host done */ +#define MID_SERVICE 0x00000004 /* something in service list */ +#define MID_SUNI_INT 0x00000002 /* interrupt from SUNI */ +#define MID_STAT_OVFL 0x00000001 /* statistics overflow */ + +/* + * Master Control/Status + */ + +#define MID_MC_S 0x04 + +#define MID_INT_SELECT 0x000001C0 /* Interrupt level (000: off) */ +#define MID_INT_SEL_SHIFT 6 +#define MID_TX_LOCK_MODE 0x00000020 /* 0: streaming; 1: TX ovfl->lock */ +#define MID_DMA_ENABLE 0x00000010 /* R: 0: disable; 1: enable + W: 0: no change; 1: enable */ +#define MID_TX_ENABLE 0x00000008 /* R: 0: TX disabled; 1: enabled + W: 0: no change; 1: enable */ +#define MID_RX_ENABLE 0x00000004 /* like TX */ +#define MID_WAIT_1MS 0x00000002 /* R: 0: timer not running; 1: running + W: 0: no change; 1: no interrupts + for 1 ms */ +#define MID_WAIT_500US 0x00000001 /* like WAIT_1MS, but 0.5 ms */ + +/* + * Statistics + * + * Cleared when reading. + */ + +#define MID_STAT 0x05 + +#define MID_VCI_TRASH 0xFFFF0000 /* trashed cells because of VCI mode */ +#define MID_VCI_TRASH_SHIFT 16 +#define MID_OVFL_TRASH 0x0000FFFF /* trashed cells because of overflow */ + +/* + * Address registers + */ + +#define MID_SERV_WRITE 0x06 /* free pos in service area (R, 10 bits) */ +#define MID_DMA_ADDR 0x07 /* virtual DMA address (R, 32 bits) */ +#define MID_DMA_WR_RX 0x08 /* (RW, 9 bits) */ +#define MID_DMA_RD_RX 0x09 +#define MID_DMA_WR_TX 0x0A +#define MID_DMA_RD_TX 0x0B + +/* + * Transmit Place Registers (0x10+4*channel) + */ + +#define MID_TX_PLACE(c) (0x10+4*(c)) + +#define MID_SIZE 0x00003800 /* size, N*256 x 32 bit */ +#define MID_SIZE_SHIFT 11 +#define MID_LOCATION 0x000007FF /* location in adapter memory (word) */ + +#define MID_LOC_SKIP 8 /* 8 bits of location are always zero + (applies to all uses of location) */ + +/* + * Transmit ReadPtr Registers (0x11+4*channel) + */ + +#define MID_TX_RDPTR(c) (0x11+4*(c)) + +#define MID_READ_PTR 0x00007FFF /* next word for PHY */ + +/* + * Transmit DescrStart Registers (0x12+4*channel) + */ + +#define MID_TX_DESCRSTART(c) (0x12+4*(c)) + +#define MID_DESCR_START 0x00007FFF /* seg buffer being DMAed */ + +#define ENI155_MAGIC 0xa54b872d + +struct midway_eprom { + unsigned char mac[MAC_LEN],inv_mac[MAC_LEN]; + unsigned char pad[36]; + u32 serial,inv_serial; + u32 magic,inv_magic; +}; + + +/* + * VCI table entry + */ + +#define MID_VCI_IN_SERVICE 0x00000001 /* set if VCI is currently in + service list */ +#define MID_VCI_SIZE 0x00038000 /* reassembly buffer size, + 2* kB */ +#define MID_VCI_SIZE_SHIFT 15 +#define MID_VCI_LOCATION 0x1ffc0000 /* buffer location */ +#define MID_VCI_LOCATION_SHIFT 18 +#define MID_VCI_PTI_MODE 0x20000000 /* 0: trash, 1: preserve */ +#define MID_VCI_MODE 0xc0000000 +#define MID_VCI_MODE_SHIFT 30 +#define MID_VCI_READ 0x00007fff +#define MID_VCI_READ_SHIFT 0 +#define MID_VCI_DESCR 0x7fff0000 +#define MID_VCI_DESCR_SHIFT 16 +#define MID_VCI_COUNT 0x000007ff +#define MID_VCI_COUNT_SHIFT 0 +#define MID_VCI_STATE 0x0000c000 +#define MID_VCI_STATE_SHIFT 14 +#define MID_VCI_WRITE 0x7fff0000 +#define MID_VCI_WRITE_SHIFT 16 + +#define MID_MODE_TRASH 0 +#define MID_MODE_RAW 1 +#define MID_MODE_AAL5 2 + +/* + * Reassembly buffer descriptor + */ + +#define MID_RED_COUNT 0x000007ff +#define MID_RED_CRC_ERR 0x00000800 +#define MID_RED_T 0x00001000 +#define MID_RED_CE 0x00010000 +#define MID_RED_CLP 0x01000000 +#define MID_RED_IDEN 0xfe000000 +#define MID_RED_SHIFT 25 + +#define MID_RED_RX_ID 0x1b /* constant identifier */ + +/* + * Segmentation buffer descriptor + */ + +#define MID_SEG_COUNT MID_RED_COUNT +#define MID_SEG_RATE 0x01f80000 +#define MID_SEG_RATE_SHIFT 19 +#define MID_SEG_PR 0x06000000 +#define MID_SEG_PR_SHIFT 25 +#define MID_SEG_AAL5 0x08000000 +#define MID_SEG_ID 0xf0000000 +#define MID_SEG_ID_SHIFT 28 +#define MID_SEG_MAX_RATE 63 + +#define MID_SEG_CLP 0x00000001 +#define MID_SEG_PTI 0x0000000e +#define MID_SEG_PTI_SHIFT 1 +#define MID_SEG_VCI 0x00003ff0 +#define MID_SEG_VCI_SHIFT 4 + +#define MID_SEG_TX_ID 0xb /* constant identifier */ + +/* + * DMA entry + */ + +#define MID_DMA_COUNT 0xffff0000 +#define MID_DMA_COUNT_SHIFT 16 +#define MID_DMA_END 0x00000020 +#define MID_DMA_TYPE 0x0000000f + +#define MID_DT_JK 0x3 +#define MID_DT_WORD 0x0 +#define MID_DT_2W 0x7 +#define MID_DT_4W 0x4 +#define MID_DT_8W 0x5 +#define MID_DT_16W 0x6 +#define MID_DT_2WM 0xf +#define MID_DT_4WM 0xc +#define MID_DT_8WM 0xd +#define MID_DT_16WM 0xe + +/* only for RX*/ +#define MID_DMA_VCI 0x0000ffc0 +#define MID_DMA_VCI_SHIFT 6 + +/* only for TX */ +#define MID_DMA_CHAN 0x000001c0 +#define MID_DMA_CHAN_SHIFT 6 + +#define MID_DT_BYTE 0x1 +#define MID_DT_HWORD 0x2 + +#endif diff --git a/drivers/atm/nicstar.c b/drivers/atm/nicstar.c new file mode 100644 index 000000000..6eb4ed256 --- /dev/null +++ b/drivers/atm/nicstar.c @@ -0,0 +1,2752 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nicstar.c + * + * Device driver supporting CBR for IDT 77201/77211 "NICStAR" based cards. + * + * IMPORTANT: The included file nicstarmac.c was NOT WRITTEN BY ME. + * It was taken from the frle-0.22 device driver. + * As the file doesn't have a copyright notice, in the file + * nicstarmac.copyright I put the copyright notice from the + * frle-0.22 device driver. + * Some code is based on the nicstar driver by M. Welsh. + * + * Author: Rui Prior (rprior@inescn.pt) + * PowerPC support by Jay Talbott (jay_talbott@mcg.mot.com) April 1999 + * + * + * (C) INESC 1999 + */ + +/* + * IMPORTANT INFORMATION + * + * There are currently three types of spinlocks: + * + * 1 - Per card interrupt spinlock (to protect structures and such) + * 2 - Per SCQ scq spinlock + * 3 - Per card resource spinlock (to access registers, etc.) + * + * These must NEVER be grabbed in reverse order. + * + */ + +/* Header files */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nicstar.h" +#ifdef CONFIG_ATM_NICSTAR_USE_SUNI +#include "suni.h" +#endif /* CONFIG_ATM_NICSTAR_USE_SUNI */ +#ifdef CONFIG_ATM_NICSTAR_USE_IDT77105 +#include "idt77105.h" +#endif /* CONFIG_ATM_NICSTAR_USE_IDT77105 */ + +/* Additional code */ + +#include "nicstarmac.c" + +/* Configurable parameters */ + +#undef PHY_LOOPBACK +#undef TX_DEBUG +#undef RX_DEBUG +#undef GENERAL_DEBUG +#undef EXTRA_DEBUG + +/* Do not touch these */ + +#ifdef TX_DEBUG +#define TXPRINTK(args...) printk(args) +#else +#define TXPRINTK(args...) +#endif /* TX_DEBUG */ + +#ifdef RX_DEBUG +#define RXPRINTK(args...) printk(args) +#else +#define RXPRINTK(args...) +#endif /* RX_DEBUG */ + +#ifdef GENERAL_DEBUG +#define PRINTK(args...) printk(args) +#else +#define PRINTK(args...) do {} while (0) +#endif /* GENERAL_DEBUG */ + +#ifdef EXTRA_DEBUG +#define XPRINTK(args...) printk(args) +#else +#define XPRINTK(args...) +#endif /* EXTRA_DEBUG */ + +/* Macros */ + +#define CMD_BUSY(card) (readl((card)->membase + STAT) & NS_STAT_CMDBZ) + +#define NS_DELAY mdelay(1) + +#define PTR_DIFF(a, b) ((u32)((unsigned long)(a) - (unsigned long)(b))) + +#ifndef ATM_SKB +#define ATM_SKB(s) (&(s)->atm) +#endif + +#define scq_virt_to_bus(scq, p) \ + (scq->dma + ((unsigned long)(p) - (unsigned long)(scq)->org)) + +/* Function declarations */ + +static u32 ns_read_sram(ns_dev * card, u32 sram_address); +static void ns_write_sram(ns_dev * card, u32 sram_address, u32 * value, + int count); +static int ns_init_card(int i, struct pci_dev *pcidev); +static void ns_init_card_error(ns_dev * card, int error); +static scq_info *get_scq(ns_dev *card, int size, u32 scd); +static void free_scq(ns_dev *card, scq_info * scq, struct atm_vcc *vcc); +static void push_rxbufs(ns_dev *, struct sk_buff *); +static irqreturn_t ns_irq_handler(int irq, void *dev_id); +static int ns_open(struct atm_vcc *vcc); +static void ns_close(struct atm_vcc *vcc); +static void fill_tst(ns_dev * card, int n, vc_map * vc); +static int ns_send(struct atm_vcc *vcc, struct sk_buff *skb); +static int push_scqe(ns_dev * card, vc_map * vc, scq_info * scq, ns_scqe * tbd, + struct sk_buff *skb); +static void process_tsq(ns_dev * card); +static void drain_scq(ns_dev * card, scq_info * scq, int pos); +static void process_rsq(ns_dev * card); +static void dequeue_rx(ns_dev * card, ns_rsqe * rsqe); +static void recycle_rx_buf(ns_dev * card, struct sk_buff *skb); +static void recycle_iovec_rx_bufs(ns_dev * card, struct iovec *iov, int count); +static void recycle_iov_buf(ns_dev * card, struct sk_buff *iovb); +static void dequeue_sm_buf(ns_dev * card, struct sk_buff *sb); +static void dequeue_lg_buf(ns_dev * card, struct sk_buff *lb); +static int ns_proc_read(struct atm_dev *dev, loff_t * pos, char *page); +static int ns_ioctl(struct atm_dev *dev, unsigned int cmd, void __user * arg); +#ifdef EXTRA_DEBUG +static void which_list(ns_dev * card, struct sk_buff *skb); +#endif +static void ns_poll(struct timer_list *unused); +static void ns_phy_put(struct atm_dev *dev, unsigned char value, + unsigned long addr); +static unsigned char ns_phy_get(struct atm_dev *dev, unsigned long addr); + +/* Global variables */ + +static struct ns_dev *cards[NS_MAX_CARDS]; +static unsigned num_cards; +static const struct atmdev_ops atm_ops = { + .open = ns_open, + .close = ns_close, + .ioctl = ns_ioctl, + .send = ns_send, + .phy_put = ns_phy_put, + .phy_get = ns_phy_get, + .proc_read = ns_proc_read, + .owner = THIS_MODULE, +}; + +static struct timer_list ns_timer; +static char *mac[NS_MAX_CARDS]; +module_param_array(mac, charp, NULL, 0); +MODULE_LICENSE("GPL"); + +/* Functions */ + +static int nicstar_init_one(struct pci_dev *pcidev, + const struct pci_device_id *ent) +{ + static int index = -1; + unsigned int error; + + index++; + cards[index] = NULL; + + error = ns_init_card(index, pcidev); + if (error) { + cards[index--] = NULL; /* don't increment index */ + goto err_out; + } + + return 0; +err_out: + return -ENODEV; +} + +static void nicstar_remove_one(struct pci_dev *pcidev) +{ + int i, j; + ns_dev *card = pci_get_drvdata(pcidev); + struct sk_buff *hb; + struct sk_buff *iovb; + struct sk_buff *lb; + struct sk_buff *sb; + + i = card->index; + + if (cards[i] == NULL) + return; + + if (card->atmdev->phy && card->atmdev->phy->stop) + card->atmdev->phy->stop(card->atmdev); + + /* Stop everything */ + writel(0x00000000, card->membase + CFG); + + /* De-register device */ + atm_dev_deregister(card->atmdev); + + /* Disable PCI device */ + pci_disable_device(pcidev); + + /* Free up resources */ + j = 0; + PRINTK("nicstar%d: freeing %d huge buffers.\n", i, card->hbpool.count); + while ((hb = skb_dequeue(&card->hbpool.queue)) != NULL) { + dev_kfree_skb_any(hb); + j++; + } + PRINTK("nicstar%d: %d huge buffers freed.\n", i, j); + j = 0; + PRINTK("nicstar%d: freeing %d iovec buffers.\n", i, + card->iovpool.count); + while ((iovb = skb_dequeue(&card->iovpool.queue)) != NULL) { + dev_kfree_skb_any(iovb); + j++; + } + PRINTK("nicstar%d: %d iovec buffers freed.\n", i, j); + while ((lb = skb_dequeue(&card->lbpool.queue)) != NULL) + dev_kfree_skb_any(lb); + while ((sb = skb_dequeue(&card->sbpool.queue)) != NULL) + dev_kfree_skb_any(sb); + free_scq(card, card->scq0, NULL); + for (j = 0; j < NS_FRSCD_NUM; j++) { + if (card->scd2vc[j] != NULL) + free_scq(card, card->scd2vc[j]->scq, card->scd2vc[j]->tx_vcc); + } + idr_destroy(&card->idr); + dma_free_coherent(&card->pcidev->dev, NS_RSQSIZE + NS_RSQ_ALIGNMENT, + card->rsq.org, card->rsq.dma); + dma_free_coherent(&card->pcidev->dev, NS_TSQSIZE + NS_TSQ_ALIGNMENT, + card->tsq.org, card->tsq.dma); + free_irq(card->pcidev->irq, card); + iounmap(card->membase); + kfree(card); +} + +static const struct pci_device_id nicstar_pci_tbl[] = { + { PCI_VDEVICE(IDT, PCI_DEVICE_ID_IDT_IDT77201), 0 }, + {0,} /* terminate list */ +}; + +MODULE_DEVICE_TABLE(pci, nicstar_pci_tbl); + +static struct pci_driver nicstar_driver = { + .name = "nicstar", + .id_table = nicstar_pci_tbl, + .probe = nicstar_init_one, + .remove = nicstar_remove_one, +}; + +static int __init nicstar_init(void) +{ + unsigned error = 0; /* Initialized to remove compile warning */ + + XPRINTK("nicstar: nicstar_init() called.\n"); + + error = pci_register_driver(&nicstar_driver); + + TXPRINTK("nicstar: TX debug enabled.\n"); + RXPRINTK("nicstar: RX debug enabled.\n"); + PRINTK("nicstar: General debug enabled.\n"); +#ifdef PHY_LOOPBACK + printk("nicstar: using PHY loopback.\n"); +#endif /* PHY_LOOPBACK */ + XPRINTK("nicstar: nicstar_init() returned.\n"); + + if (!error) { + timer_setup(&ns_timer, ns_poll, 0); + ns_timer.expires = jiffies + NS_POLL_PERIOD; + add_timer(&ns_timer); + } + + return error; +} + +static void __exit nicstar_cleanup(void) +{ + XPRINTK("nicstar: nicstar_cleanup() called.\n"); + + del_timer_sync(&ns_timer); + + pci_unregister_driver(&nicstar_driver); + + XPRINTK("nicstar: nicstar_cleanup() returned.\n"); +} + +static u32 ns_read_sram(ns_dev * card, u32 sram_address) +{ + unsigned long flags; + u32 data; + sram_address <<= 2; + sram_address &= 0x0007FFFC; /* address must be dword aligned */ + sram_address |= 0x50000000; /* SRAM read command */ + spin_lock_irqsave(&card->res_lock, flags); + while (CMD_BUSY(card)) ; + writel(sram_address, card->membase + CMD); + while (CMD_BUSY(card)) ; + data = readl(card->membase + DR0); + spin_unlock_irqrestore(&card->res_lock, flags); + return data; +} + +static void ns_write_sram(ns_dev * card, u32 sram_address, u32 * value, + int count) +{ + unsigned long flags; + int i, c; + count--; /* count range now is 0..3 instead of 1..4 */ + c = count; + c <<= 2; /* to use increments of 4 */ + spin_lock_irqsave(&card->res_lock, flags); + while (CMD_BUSY(card)) ; + for (i = 0; i <= c; i += 4) + writel(*(value++), card->membase + i); + /* Note: DR# registers are the first 4 dwords in nicstar's memspace, + so card->membase + DR0 == card->membase */ + sram_address <<= 2; + sram_address &= 0x0007FFFC; + sram_address |= (0x40000000 | count); + writel(sram_address, card->membase + CMD); + spin_unlock_irqrestore(&card->res_lock, flags); +} + +static int ns_init_card(int i, struct pci_dev *pcidev) +{ + int j; + struct ns_dev *card = NULL; + unsigned char pci_latency; + unsigned error; + u32 data; + u32 u32d[4]; + u32 ns_cfg_rctsize; + int bcount; + unsigned long membase; + + error = 0; + + if (pci_enable_device(pcidev)) { + printk("nicstar%d: can't enable PCI device\n", i); + error = 2; + ns_init_card_error(card, error); + return error; + } + if (dma_set_mask_and_coherent(&pcidev->dev, DMA_BIT_MASK(32)) != 0) { + printk(KERN_WARNING + "nicstar%d: No suitable DMA available.\n", i); + error = 2; + ns_init_card_error(card, error); + return error; + } + + card = kmalloc(sizeof(*card), GFP_KERNEL); + if (!card) { + printk + ("nicstar%d: can't allocate memory for device structure.\n", + i); + error = 2; + ns_init_card_error(card, error); + return error; + } + cards[i] = card; + spin_lock_init(&card->int_lock); + spin_lock_init(&card->res_lock); + + pci_set_drvdata(pcidev, card); + + card->index = i; + card->atmdev = NULL; + card->pcidev = pcidev; + membase = pci_resource_start(pcidev, 1); + card->membase = ioremap(membase, NS_IOREMAP_SIZE); + if (!card->membase) { + printk("nicstar%d: can't ioremap() membase.\n", i); + error = 3; + ns_init_card_error(card, error); + return error; + } + PRINTK("nicstar%d: membase at 0x%p.\n", i, card->membase); + + pci_set_master(pcidev); + + if (pci_read_config_byte(pcidev, PCI_LATENCY_TIMER, &pci_latency) != 0) { + printk("nicstar%d: can't read PCI latency timer.\n", i); + error = 6; + ns_init_card_error(card, error); + return error; + } +#ifdef NS_PCI_LATENCY + if (pci_latency < NS_PCI_LATENCY) { + PRINTK("nicstar%d: setting PCI latency timer to %d.\n", i, + NS_PCI_LATENCY); + for (j = 1; j < 4; j++) { + if (pci_write_config_byte + (pcidev, PCI_LATENCY_TIMER, NS_PCI_LATENCY) != 0) + break; + } + if (j == 4) { + printk + ("nicstar%d: can't set PCI latency timer to %d.\n", + i, NS_PCI_LATENCY); + error = 7; + ns_init_card_error(card, error); + return error; + } + } +#endif /* NS_PCI_LATENCY */ + + /* Clear timer overflow */ + data = readl(card->membase + STAT); + if (data & NS_STAT_TMROF) + writel(NS_STAT_TMROF, card->membase + STAT); + + /* Software reset */ + writel(NS_CFG_SWRST, card->membase + CFG); + NS_DELAY; + writel(0x00000000, card->membase + CFG); + + /* PHY reset */ + writel(0x00000008, card->membase + GP); + NS_DELAY; + writel(0x00000001, card->membase + GP); + NS_DELAY; + while (CMD_BUSY(card)) ; + writel(NS_CMD_WRITE_UTILITY | 0x00000100, card->membase + CMD); /* Sync UTOPIA with SAR clock */ + NS_DELAY; + + /* Detect PHY type */ + while (CMD_BUSY(card)) ; + writel(NS_CMD_READ_UTILITY | 0x00000200, card->membase + CMD); + while (CMD_BUSY(card)) ; + data = readl(card->membase + DR0); + switch (data) { + case 0x00000009: + printk("nicstar%d: PHY seems to be 25 Mbps.\n", i); + card->max_pcr = ATM_25_PCR; + while (CMD_BUSY(card)) ; + writel(0x00000008, card->membase + DR0); + writel(NS_CMD_WRITE_UTILITY | 0x00000200, card->membase + CMD); + /* Clear an eventual pending interrupt */ + writel(NS_STAT_SFBQF, card->membase + STAT); +#ifdef PHY_LOOPBACK + while (CMD_BUSY(card)) ; + writel(0x00000022, card->membase + DR0); + writel(NS_CMD_WRITE_UTILITY | 0x00000202, card->membase + CMD); +#endif /* PHY_LOOPBACK */ + break; + case 0x00000030: + case 0x00000031: + printk("nicstar%d: PHY seems to be 155 Mbps.\n", i); + card->max_pcr = ATM_OC3_PCR; +#ifdef PHY_LOOPBACK + while (CMD_BUSY(card)) ; + writel(0x00000002, card->membase + DR0); + writel(NS_CMD_WRITE_UTILITY | 0x00000205, card->membase + CMD); +#endif /* PHY_LOOPBACK */ + break; + default: + printk("nicstar%d: unknown PHY type (0x%08X).\n", i, data); + error = 8; + ns_init_card_error(card, error); + return error; + } + writel(0x00000000, card->membase + GP); + + /* Determine SRAM size */ + data = 0x76543210; + ns_write_sram(card, 0x1C003, &data, 1); + data = 0x89ABCDEF; + ns_write_sram(card, 0x14003, &data, 1); + if (ns_read_sram(card, 0x14003) == 0x89ABCDEF && + ns_read_sram(card, 0x1C003) == 0x76543210) + card->sram_size = 128; + else + card->sram_size = 32; + PRINTK("nicstar%d: %dK x 32bit SRAM size.\n", i, card->sram_size); + + card->rct_size = NS_MAX_RCTSIZE; + +#if (NS_MAX_RCTSIZE == 4096) + if (card->sram_size == 128) + printk + ("nicstar%d: limiting maximum VCI. See NS_MAX_RCTSIZE in nicstar.h\n", + i); +#elif (NS_MAX_RCTSIZE == 16384) + if (card->sram_size == 32) { + printk + ("nicstar%d: wasting memory. See NS_MAX_RCTSIZE in nicstar.h\n", + i); + card->rct_size = 4096; + } +#else +#error NS_MAX_RCTSIZE must be either 4096 or 16384 in nicstar.c +#endif + + card->vpibits = NS_VPIBITS; + if (card->rct_size == 4096) + card->vcibits = 12 - NS_VPIBITS; + else /* card->rct_size == 16384 */ + card->vcibits = 14 - NS_VPIBITS; + + /* Initialize the nicstar eeprom/eprom stuff, for the MAC addr */ + if (mac[i] == NULL) + nicstar_init_eprom(card->membase); + + /* Set the VPI/VCI MSb mask to zero so we can receive OAM cells */ + writel(0x00000000, card->membase + VPM); + + card->intcnt = 0; + if (request_irq + (pcidev->irq, &ns_irq_handler, IRQF_SHARED, "nicstar", card) != 0) { + pr_err("nicstar%d: can't allocate IRQ %d.\n", i, pcidev->irq); + error = 9; + ns_init_card_error(card, error); + return error; + } + + /* Initialize TSQ */ + card->tsq.org = dma_alloc_coherent(&card->pcidev->dev, + NS_TSQSIZE + NS_TSQ_ALIGNMENT, + &card->tsq.dma, GFP_KERNEL); + if (card->tsq.org == NULL) { + printk("nicstar%d: can't allocate TSQ.\n", i); + error = 10; + ns_init_card_error(card, error); + return error; + } + card->tsq.base = PTR_ALIGN(card->tsq.org, NS_TSQ_ALIGNMENT); + card->tsq.next = card->tsq.base; + card->tsq.last = card->tsq.base + (NS_TSQ_NUM_ENTRIES - 1); + for (j = 0; j < NS_TSQ_NUM_ENTRIES; j++) + ns_tsi_init(card->tsq.base + j); + writel(0x00000000, card->membase + TSQH); + writel(ALIGN(card->tsq.dma, NS_TSQ_ALIGNMENT), card->membase + TSQB); + PRINTK("nicstar%d: TSQ base at 0x%p.\n", i, card->tsq.base); + + /* Initialize RSQ */ + card->rsq.org = dma_alloc_coherent(&card->pcidev->dev, + NS_RSQSIZE + NS_RSQ_ALIGNMENT, + &card->rsq.dma, GFP_KERNEL); + if (card->rsq.org == NULL) { + printk("nicstar%d: can't allocate RSQ.\n", i); + error = 11; + ns_init_card_error(card, error); + return error; + } + card->rsq.base = PTR_ALIGN(card->rsq.org, NS_RSQ_ALIGNMENT); + card->rsq.next = card->rsq.base; + card->rsq.last = card->rsq.base + (NS_RSQ_NUM_ENTRIES - 1); + for (j = 0; j < NS_RSQ_NUM_ENTRIES; j++) + ns_rsqe_init(card->rsq.base + j); + writel(0x00000000, card->membase + RSQH); + writel(ALIGN(card->rsq.dma, NS_RSQ_ALIGNMENT), card->membase + RSQB); + PRINTK("nicstar%d: RSQ base at 0x%p.\n", i, card->rsq.base); + + /* Initialize SCQ0, the only VBR SCQ used */ + card->scq1 = NULL; + card->scq2 = NULL; + card->scq0 = get_scq(card, VBR_SCQSIZE, NS_VRSCD0); + if (card->scq0 == NULL) { + printk("nicstar%d: can't get SCQ0.\n", i); + error = 12; + ns_init_card_error(card, error); + return error; + } + u32d[0] = scq_virt_to_bus(card->scq0, card->scq0->base); + u32d[1] = (u32) 0x00000000; + u32d[2] = (u32) 0xffffffff; + u32d[3] = (u32) 0x00000000; + ns_write_sram(card, NS_VRSCD0, u32d, 4); + ns_write_sram(card, NS_VRSCD1, u32d, 4); /* These last two won't be used */ + ns_write_sram(card, NS_VRSCD2, u32d, 4); /* but are initialized, just in case... */ + card->scq0->scd = NS_VRSCD0; + PRINTK("nicstar%d: VBR-SCQ0 base at 0x%p.\n", i, card->scq0->base); + + /* Initialize TSTs */ + card->tst_addr = NS_TST0; + card->tst_free_entries = NS_TST_NUM_ENTRIES; + data = NS_TST_OPCODE_VARIABLE; + for (j = 0; j < NS_TST_NUM_ENTRIES; j++) + ns_write_sram(card, NS_TST0 + j, &data, 1); + data = ns_tste_make(NS_TST_OPCODE_END, NS_TST0); + ns_write_sram(card, NS_TST0 + NS_TST_NUM_ENTRIES, &data, 1); + for (j = 0; j < NS_TST_NUM_ENTRIES; j++) + ns_write_sram(card, NS_TST1 + j, &data, 1); + data = ns_tste_make(NS_TST_OPCODE_END, NS_TST1); + ns_write_sram(card, NS_TST1 + NS_TST_NUM_ENTRIES, &data, 1); + for (j = 0; j < NS_TST_NUM_ENTRIES; j++) + card->tste2vc[j] = NULL; + writel(NS_TST0 << 2, card->membase + TSTB); + + /* Initialize RCT. AAL type is set on opening the VC. */ +#ifdef RCQ_SUPPORT + u32d[0] = NS_RCTE_RAWCELLINTEN; +#else + u32d[0] = 0x00000000; +#endif /* RCQ_SUPPORT */ + u32d[1] = 0x00000000; + u32d[2] = 0x00000000; + u32d[3] = 0xFFFFFFFF; + for (j = 0; j < card->rct_size; j++) + ns_write_sram(card, j * 4, u32d, 4); + + memset(card->vcmap, 0, sizeof(card->vcmap)); + + for (j = 0; j < NS_FRSCD_NUM; j++) + card->scd2vc[j] = NULL; + + /* Initialize buffer levels */ + card->sbnr.min = MIN_SB; + card->sbnr.init = NUM_SB; + card->sbnr.max = MAX_SB; + card->lbnr.min = MIN_LB; + card->lbnr.init = NUM_LB; + card->lbnr.max = MAX_LB; + card->iovnr.min = MIN_IOVB; + card->iovnr.init = NUM_IOVB; + card->iovnr.max = MAX_IOVB; + card->hbnr.min = MIN_HB; + card->hbnr.init = NUM_HB; + card->hbnr.max = MAX_HB; + + card->sm_handle = NULL; + card->sm_addr = 0x00000000; + card->lg_handle = NULL; + card->lg_addr = 0x00000000; + + card->efbie = 1; /* To prevent push_rxbufs from enabling the interrupt */ + + idr_init(&card->idr); + + /* Pre-allocate some huge buffers */ + skb_queue_head_init(&card->hbpool.queue); + card->hbpool.count = 0; + for (j = 0; j < NUM_HB; j++) { + struct sk_buff *hb; + hb = __dev_alloc_skb(NS_HBUFSIZE, GFP_KERNEL); + if (hb == NULL) { + printk + ("nicstar%d: can't allocate %dth of %d huge buffers.\n", + i, j, NUM_HB); + error = 13; + ns_init_card_error(card, error); + return error; + } + NS_PRV_BUFTYPE(hb) = BUF_NONE; + skb_queue_tail(&card->hbpool.queue, hb); + card->hbpool.count++; + } + + /* Allocate large buffers */ + skb_queue_head_init(&card->lbpool.queue); + card->lbpool.count = 0; /* Not used */ + for (j = 0; j < NUM_LB; j++) { + struct sk_buff *lb; + lb = __dev_alloc_skb(NS_LGSKBSIZE, GFP_KERNEL); + if (lb == NULL) { + printk + ("nicstar%d: can't allocate %dth of %d large buffers.\n", + i, j, NUM_LB); + error = 14; + ns_init_card_error(card, error); + return error; + } + NS_PRV_BUFTYPE(lb) = BUF_LG; + skb_queue_tail(&card->lbpool.queue, lb); + skb_reserve(lb, NS_SMBUFSIZE); + push_rxbufs(card, lb); + /* Due to the implementation of push_rxbufs() this is 1, not 0 */ + if (j == 1) { + card->rcbuf = lb; + card->rawcell = (struct ns_rcqe *) lb->data; + card->rawch = NS_PRV_DMA(lb); + } + } + /* Test for strange behaviour which leads to crashes */ + if ((bcount = + ns_stat_lfbqc_get(readl(card->membase + STAT))) < card->lbnr.min) { + printk + ("nicstar%d: Strange... Just allocated %d large buffers and lfbqc = %d.\n", + i, j, bcount); + error = 14; + ns_init_card_error(card, error); + return error; + } + + /* Allocate small buffers */ + skb_queue_head_init(&card->sbpool.queue); + card->sbpool.count = 0; /* Not used */ + for (j = 0; j < NUM_SB; j++) { + struct sk_buff *sb; + sb = __dev_alloc_skb(NS_SMSKBSIZE, GFP_KERNEL); + if (sb == NULL) { + printk + ("nicstar%d: can't allocate %dth of %d small buffers.\n", + i, j, NUM_SB); + error = 15; + ns_init_card_error(card, error); + return error; + } + NS_PRV_BUFTYPE(sb) = BUF_SM; + skb_queue_tail(&card->sbpool.queue, sb); + skb_reserve(sb, NS_AAL0_HEADER); + push_rxbufs(card, sb); + } + /* Test for strange behaviour which leads to crashes */ + if ((bcount = + ns_stat_sfbqc_get(readl(card->membase + STAT))) < card->sbnr.min) { + printk + ("nicstar%d: Strange... Just allocated %d small buffers and sfbqc = %d.\n", + i, j, bcount); + error = 15; + ns_init_card_error(card, error); + return error; + } + + /* Allocate iovec buffers */ + skb_queue_head_init(&card->iovpool.queue); + card->iovpool.count = 0; + for (j = 0; j < NUM_IOVB; j++) { + struct sk_buff *iovb; + iovb = alloc_skb(NS_IOVBUFSIZE, GFP_KERNEL); + if (iovb == NULL) { + printk + ("nicstar%d: can't allocate %dth of %d iovec buffers.\n", + i, j, NUM_IOVB); + error = 16; + ns_init_card_error(card, error); + return error; + } + NS_PRV_BUFTYPE(iovb) = BUF_NONE; + skb_queue_tail(&card->iovpool.queue, iovb); + card->iovpool.count++; + } + + /* Configure NICStAR */ + if (card->rct_size == 4096) + ns_cfg_rctsize = NS_CFG_RCTSIZE_4096_ENTRIES; + else /* (card->rct_size == 16384) */ + ns_cfg_rctsize = NS_CFG_RCTSIZE_16384_ENTRIES; + + card->efbie = 1; + + /* Register device */ + card->atmdev = atm_dev_register("nicstar", &card->pcidev->dev, &atm_ops, + -1, NULL); + if (card->atmdev == NULL) { + printk("nicstar%d: can't register device.\n", i); + error = 17; + ns_init_card_error(card, error); + return error; + } + + if (mac[i] == NULL || !mac_pton(mac[i], card->atmdev->esi)) { + nicstar_read_eprom(card->membase, NICSTAR_EPROM_MAC_ADDR_OFFSET, + card->atmdev->esi, 6); + if (ether_addr_equal(card->atmdev->esi, "\x00\x00\x00\x00\x00\x00")) { + nicstar_read_eprom(card->membase, + NICSTAR_EPROM_MAC_ADDR_OFFSET_ALT, + card->atmdev->esi, 6); + } + } + + printk("nicstar%d: MAC address %pM\n", i, card->atmdev->esi); + + card->atmdev->dev_data = card; + card->atmdev->ci_range.vpi_bits = card->vpibits; + card->atmdev->ci_range.vci_bits = card->vcibits; + card->atmdev->link_rate = card->max_pcr; + card->atmdev->phy = NULL; + +#ifdef CONFIG_ATM_NICSTAR_USE_SUNI + if (card->max_pcr == ATM_OC3_PCR) + suni_init(card->atmdev); +#endif /* CONFIG_ATM_NICSTAR_USE_SUNI */ + +#ifdef CONFIG_ATM_NICSTAR_USE_IDT77105 + if (card->max_pcr == ATM_25_PCR) + idt77105_init(card->atmdev); +#endif /* CONFIG_ATM_NICSTAR_USE_IDT77105 */ + + if (card->atmdev->phy && card->atmdev->phy->start) + card->atmdev->phy->start(card->atmdev); + + writel(NS_CFG_RXPATH | NS_CFG_SMBUFSIZE | NS_CFG_LGBUFSIZE | NS_CFG_EFBIE | NS_CFG_RSQSIZE | NS_CFG_VPIBITS | ns_cfg_rctsize | NS_CFG_RXINT_NODELAY | NS_CFG_RAWIE | /* Only enabled if RCQ_SUPPORT */ + NS_CFG_RSQAFIE | NS_CFG_TXEN | NS_CFG_TXIE | NS_CFG_TSQFIE_OPT | /* Only enabled if ENABLE_TSQFIE */ + NS_CFG_PHYIE, card->membase + CFG); + + num_cards++; + + return error; +} + +static void ns_init_card_error(ns_dev *card, int error) +{ + if (error >= 17) { + writel(0x00000000, card->membase + CFG); + } + if (error >= 16) { + struct sk_buff *iovb; + while ((iovb = skb_dequeue(&card->iovpool.queue)) != NULL) + dev_kfree_skb_any(iovb); + } + if (error >= 15) { + struct sk_buff *sb; + while ((sb = skb_dequeue(&card->sbpool.queue)) != NULL) + dev_kfree_skb_any(sb); + free_scq(card, card->scq0, NULL); + } + if (error >= 14) { + struct sk_buff *lb; + while ((lb = skb_dequeue(&card->lbpool.queue)) != NULL) + dev_kfree_skb_any(lb); + } + if (error >= 13) { + struct sk_buff *hb; + while ((hb = skb_dequeue(&card->hbpool.queue)) != NULL) + dev_kfree_skb_any(hb); + } + if (error >= 12) { + dma_free_coherent(&card->pcidev->dev, NS_RSQSIZE + NS_RSQ_ALIGNMENT, + card->rsq.org, card->rsq.dma); + } + if (error >= 11) { + dma_free_coherent(&card->pcidev->dev, NS_TSQSIZE + NS_TSQ_ALIGNMENT, + card->tsq.org, card->tsq.dma); + } + if (error >= 10) { + free_irq(card->pcidev->irq, card); + } + if (error >= 4) { + iounmap(card->membase); + } + if (error >= 3) { + pci_disable_device(card->pcidev); + kfree(card); + } +} + +static scq_info *get_scq(ns_dev *card, int size, u32 scd) +{ + scq_info *scq; + int i; + + if (size != VBR_SCQSIZE && size != CBR_SCQSIZE) + return NULL; + + scq = kmalloc(sizeof(*scq), GFP_KERNEL); + if (!scq) + return NULL; + scq->org = dma_alloc_coherent(&card->pcidev->dev, + 2 * size, &scq->dma, GFP_KERNEL); + if (!scq->org) { + kfree(scq); + return NULL; + } + scq->skb = kmalloc_array(size / NS_SCQE_SIZE, + sizeof(*scq->skb), + GFP_KERNEL); + if (!scq->skb) { + dma_free_coherent(&card->pcidev->dev, + 2 * size, scq->org, scq->dma); + kfree(scq); + return NULL; + } + scq->num_entries = size / NS_SCQE_SIZE; + scq->base = PTR_ALIGN(scq->org, size); + scq->next = scq->base; + scq->last = scq->base + (scq->num_entries - 1); + scq->tail = scq->last; + scq->scd = scd; + scq->num_entries = size / NS_SCQE_SIZE; + scq->tbd_count = 0; + init_waitqueue_head(&scq->scqfull_waitq); + scq->full = 0; + spin_lock_init(&scq->lock); + + for (i = 0; i < scq->num_entries; i++) + scq->skb[i] = NULL; + + return scq; +} + +/* For variable rate SCQ vcc must be NULL */ +static void free_scq(ns_dev *card, scq_info *scq, struct atm_vcc *vcc) +{ + int i; + + if (scq->num_entries == VBR_SCQ_NUM_ENTRIES) + for (i = 0; i < scq->num_entries; i++) { + if (scq->skb[i] != NULL) { + vcc = ATM_SKB(scq->skb[i])->vcc; + if (vcc->pop != NULL) + vcc->pop(vcc, scq->skb[i]); + else + dev_kfree_skb_any(scq->skb[i]); + } + } else { /* vcc must be != NULL */ + + if (vcc == NULL) { + printk + ("nicstar: free_scq() called with vcc == NULL for fixed rate scq."); + for (i = 0; i < scq->num_entries; i++) + dev_kfree_skb_any(scq->skb[i]); + } else + for (i = 0; i < scq->num_entries; i++) { + if (scq->skb[i] != NULL) { + if (vcc->pop != NULL) + vcc->pop(vcc, scq->skb[i]); + else + dev_kfree_skb_any(scq->skb[i]); + } + } + } + kfree(scq->skb); + dma_free_coherent(&card->pcidev->dev, + 2 * (scq->num_entries == VBR_SCQ_NUM_ENTRIES ? + VBR_SCQSIZE : CBR_SCQSIZE), + scq->org, scq->dma); + kfree(scq); +} + +/* The handles passed must be pointers to the sk_buff containing the small + or large buffer(s) cast to u32. */ +static void push_rxbufs(ns_dev * card, struct sk_buff *skb) +{ + struct sk_buff *handle1, *handle2; + int id1, id2; + u32 addr1, addr2; + u32 stat; + unsigned long flags; + + /* *BARF* */ + handle2 = NULL; + addr2 = 0; + handle1 = skb; + addr1 = dma_map_single(&card->pcidev->dev, + skb->data, + (NS_PRV_BUFTYPE(skb) == BUF_SM + ? NS_SMSKBSIZE : NS_LGSKBSIZE), + DMA_TO_DEVICE); + NS_PRV_DMA(skb) = addr1; /* save so we can unmap later */ + +#ifdef GENERAL_DEBUG + if (!addr1) + printk("nicstar%d: push_rxbufs called with addr1 = 0.\n", + card->index); +#endif /* GENERAL_DEBUG */ + + stat = readl(card->membase + STAT); + card->sbfqc = ns_stat_sfbqc_get(stat); + card->lbfqc = ns_stat_lfbqc_get(stat); + if (NS_PRV_BUFTYPE(skb) == BUF_SM) { + if (!addr2) { + if (card->sm_addr) { + addr2 = card->sm_addr; + handle2 = card->sm_handle; + card->sm_addr = 0x00000000; + card->sm_handle = NULL; + } else { /* (!sm_addr) */ + + card->sm_addr = addr1; + card->sm_handle = handle1; + } + } + } else { /* buf_type == BUF_LG */ + + if (!addr2) { + if (card->lg_addr) { + addr2 = card->lg_addr; + handle2 = card->lg_handle; + card->lg_addr = 0x00000000; + card->lg_handle = NULL; + } else { /* (!lg_addr) */ + + card->lg_addr = addr1; + card->lg_handle = handle1; + } + } + } + + if (addr2) { + if (NS_PRV_BUFTYPE(skb) == BUF_SM) { + if (card->sbfqc >= card->sbnr.max) { + skb_unlink(handle1, &card->sbpool.queue); + dev_kfree_skb_any(handle1); + skb_unlink(handle2, &card->sbpool.queue); + dev_kfree_skb_any(handle2); + return; + } else + card->sbfqc += 2; + } else { /* (buf_type == BUF_LG) */ + + if (card->lbfqc >= card->lbnr.max) { + skb_unlink(handle1, &card->lbpool.queue); + dev_kfree_skb_any(handle1); + skb_unlink(handle2, &card->lbpool.queue); + dev_kfree_skb_any(handle2); + return; + } else + card->lbfqc += 2; + } + + id1 = idr_alloc(&card->idr, handle1, 0, 0, GFP_ATOMIC); + if (id1 < 0) + goto out; + + id2 = idr_alloc(&card->idr, handle2, 0, 0, GFP_ATOMIC); + if (id2 < 0) + goto out; + + spin_lock_irqsave(&card->res_lock, flags); + while (CMD_BUSY(card)) ; + writel(addr2, card->membase + DR3); + writel(id2, card->membase + DR2); + writel(addr1, card->membase + DR1); + writel(id1, card->membase + DR0); + writel(NS_CMD_WRITE_FREEBUFQ | NS_PRV_BUFTYPE(skb), + card->membase + CMD); + spin_unlock_irqrestore(&card->res_lock, flags); + + XPRINTK("nicstar%d: Pushing %s buffers at 0x%x and 0x%x.\n", + card->index, + (NS_PRV_BUFTYPE(skb) == BUF_SM ? "small" : "large"), + addr1, addr2); + } + + if (!card->efbie && card->sbfqc >= card->sbnr.min && + card->lbfqc >= card->lbnr.min) { + card->efbie = 1; + writel((readl(card->membase + CFG) | NS_CFG_EFBIE), + card->membase + CFG); + } + +out: + return; +} + +static irqreturn_t ns_irq_handler(int irq, void *dev_id) +{ + u32 stat_r; + ns_dev *card; + struct atm_dev *dev; + unsigned long flags; + + card = (ns_dev *) dev_id; + dev = card->atmdev; + card->intcnt++; + + PRINTK("nicstar%d: NICStAR generated an interrupt\n", card->index); + + spin_lock_irqsave(&card->int_lock, flags); + + stat_r = readl(card->membase + STAT); + + /* Transmit Status Indicator has been written to T. S. Queue */ + if (stat_r & NS_STAT_TSIF) { + TXPRINTK("nicstar%d: TSI interrupt\n", card->index); + process_tsq(card); + writel(NS_STAT_TSIF, card->membase + STAT); + } + + /* Incomplete CS-PDU has been transmitted */ + if (stat_r & NS_STAT_TXICP) { + writel(NS_STAT_TXICP, card->membase + STAT); + TXPRINTK("nicstar%d: Incomplete CS-PDU transmitted.\n", + card->index); + } + + /* Transmit Status Queue 7/8 full */ + if (stat_r & NS_STAT_TSQF) { + writel(NS_STAT_TSQF, card->membase + STAT); + PRINTK("nicstar%d: TSQ full.\n", card->index); + process_tsq(card); + } + + /* Timer overflow */ + if (stat_r & NS_STAT_TMROF) { + writel(NS_STAT_TMROF, card->membase + STAT); + PRINTK("nicstar%d: Timer overflow.\n", card->index); + } + + /* PHY device interrupt signal active */ + if (stat_r & NS_STAT_PHYI) { + writel(NS_STAT_PHYI, card->membase + STAT); + PRINTK("nicstar%d: PHY interrupt.\n", card->index); + if (dev->phy && dev->phy->interrupt) { + dev->phy->interrupt(dev); + } + } + + /* Small Buffer Queue is full */ + if (stat_r & NS_STAT_SFBQF) { + writel(NS_STAT_SFBQF, card->membase + STAT); + printk("nicstar%d: Small free buffer queue is full.\n", + card->index); + } + + /* Large Buffer Queue is full */ + if (stat_r & NS_STAT_LFBQF) { + writel(NS_STAT_LFBQF, card->membase + STAT); + printk("nicstar%d: Large free buffer queue is full.\n", + card->index); + } + + /* Receive Status Queue is full */ + if (stat_r & NS_STAT_RSQF) { + writel(NS_STAT_RSQF, card->membase + STAT); + printk("nicstar%d: RSQ full.\n", card->index); + process_rsq(card); + } + + /* Complete CS-PDU received */ + if (stat_r & NS_STAT_EOPDU) { + RXPRINTK("nicstar%d: End of CS-PDU received.\n", card->index); + process_rsq(card); + writel(NS_STAT_EOPDU, card->membase + STAT); + } + + /* Raw cell received */ + if (stat_r & NS_STAT_RAWCF) { + writel(NS_STAT_RAWCF, card->membase + STAT); +#ifndef RCQ_SUPPORT + printk("nicstar%d: Raw cell received and no support yet...\n", + card->index); +#endif /* RCQ_SUPPORT */ + /* NOTE: the following procedure may keep a raw cell pending until the + next interrupt. As this preliminary support is only meant to + avoid buffer leakage, this is not an issue. */ + while (readl(card->membase + RAWCT) != card->rawch) { + + if (ns_rcqe_islast(card->rawcell)) { + struct sk_buff *oldbuf; + + oldbuf = card->rcbuf; + card->rcbuf = idr_find(&card->idr, + ns_rcqe_nextbufhandle(card->rawcell)); + card->rawch = NS_PRV_DMA(card->rcbuf); + card->rawcell = (struct ns_rcqe *) + card->rcbuf->data; + recycle_rx_buf(card, oldbuf); + } else { + card->rawch += NS_RCQE_SIZE; + card->rawcell++; + } + } + } + + /* Small buffer queue is empty */ + if (stat_r & NS_STAT_SFBQE) { + int i; + struct sk_buff *sb; + + writel(NS_STAT_SFBQE, card->membase + STAT); + printk("nicstar%d: Small free buffer queue empty.\n", + card->index); + for (i = 0; i < card->sbnr.min; i++) { + sb = dev_alloc_skb(NS_SMSKBSIZE); + if (sb == NULL) { + writel(readl(card->membase + CFG) & + ~NS_CFG_EFBIE, card->membase + CFG); + card->efbie = 0; + break; + } + NS_PRV_BUFTYPE(sb) = BUF_SM; + skb_queue_tail(&card->sbpool.queue, sb); + skb_reserve(sb, NS_AAL0_HEADER); + push_rxbufs(card, sb); + } + card->sbfqc = i; + process_rsq(card); + } + + /* Large buffer queue empty */ + if (stat_r & NS_STAT_LFBQE) { + int i; + struct sk_buff *lb; + + writel(NS_STAT_LFBQE, card->membase + STAT); + printk("nicstar%d: Large free buffer queue empty.\n", + card->index); + for (i = 0; i < card->lbnr.min; i++) { + lb = dev_alloc_skb(NS_LGSKBSIZE); + if (lb == NULL) { + writel(readl(card->membase + CFG) & + ~NS_CFG_EFBIE, card->membase + CFG); + card->efbie = 0; + break; + } + NS_PRV_BUFTYPE(lb) = BUF_LG; + skb_queue_tail(&card->lbpool.queue, lb); + skb_reserve(lb, NS_SMBUFSIZE); + push_rxbufs(card, lb); + } + card->lbfqc = i; + process_rsq(card); + } + + /* Receive Status Queue is 7/8 full */ + if (stat_r & NS_STAT_RSQAF) { + writel(NS_STAT_RSQAF, card->membase + STAT); + RXPRINTK("nicstar%d: RSQ almost full.\n", card->index); + process_rsq(card); + } + + spin_unlock_irqrestore(&card->int_lock, flags); + PRINTK("nicstar%d: end of interrupt service\n", card->index); + return IRQ_HANDLED; +} + +static int ns_open(struct atm_vcc *vcc) +{ + ns_dev *card; + vc_map *vc; + unsigned long tmpl, modl; + int tcr, tcra; /* target cell rate, and absolute value */ + int n = 0; /* Number of entries in the TST. Initialized to remove + the compiler warning. */ + u32 u32d[4]; + int frscdi = 0; /* Index of the SCD. Initialized to remove the compiler + warning. How I wish compilers were clever enough to + tell which variables can truly be used + uninitialized... */ + int inuse; /* tx or rx vc already in use by another vcc */ + short vpi = vcc->vpi; + int vci = vcc->vci; + + card = (ns_dev *) vcc->dev->dev_data; + PRINTK("nicstar%d: opening vpi.vci %d.%d \n", card->index, (int)vpi, + vci); + if (vcc->qos.aal != ATM_AAL5 && vcc->qos.aal != ATM_AAL0) { + PRINTK("nicstar%d: unsupported AAL.\n", card->index); + return -EINVAL; + } + + vc = &(card->vcmap[vpi << card->vcibits | vci]); + vcc->dev_data = vc; + + inuse = 0; + if (vcc->qos.txtp.traffic_class != ATM_NONE && vc->tx) + inuse = 1; + if (vcc->qos.rxtp.traffic_class != ATM_NONE && vc->rx) + inuse += 2; + if (inuse) { + printk("nicstar%d: %s vci already in use.\n", card->index, + inuse == 1 ? "tx" : inuse == 2 ? "rx" : "tx and rx"); + return -EINVAL; + } + + set_bit(ATM_VF_ADDR, &vcc->flags); + + /* NOTE: You are not allowed to modify an open connection's QOS. To change + that, remove the ATM_VF_PARTIAL flag checking. There may be other changes + needed to do that. */ + if (!test_bit(ATM_VF_PARTIAL, &vcc->flags)) { + scq_info *scq; + + set_bit(ATM_VF_PARTIAL, &vcc->flags); + if (vcc->qos.txtp.traffic_class == ATM_CBR) { + /* Check requested cell rate and availability of SCD */ + if (vcc->qos.txtp.max_pcr == 0 && vcc->qos.txtp.pcr == 0 + && vcc->qos.txtp.min_pcr == 0) { + PRINTK + ("nicstar%d: trying to open a CBR vc with cell rate = 0 \n", + card->index); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + return -EINVAL; + } + + tcr = atm_pcr_goal(&(vcc->qos.txtp)); + tcra = tcr >= 0 ? tcr : -tcr; + + PRINTK("nicstar%d: target cell rate = %d.\n", + card->index, vcc->qos.txtp.max_pcr); + + tmpl = + (unsigned long)tcra *(unsigned long) + NS_TST_NUM_ENTRIES; + modl = tmpl % card->max_pcr; + + n = (int)(tmpl / card->max_pcr); + if (tcr > 0) { + if (modl > 0) + n++; + } else if (tcr == 0) { + if ((n = + (card->tst_free_entries - + NS_TST_RESERVED)) <= 0) { + PRINTK + ("nicstar%d: no CBR bandwidth free.\n", + card->index); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + return -EINVAL; + } + } + + if (n == 0) { + printk + ("nicstar%d: selected bandwidth < granularity.\n", + card->index); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + return -EINVAL; + } + + if (n > (card->tst_free_entries - NS_TST_RESERVED)) { + PRINTK + ("nicstar%d: not enough free CBR bandwidth.\n", + card->index); + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + return -EINVAL; + } else + card->tst_free_entries -= n; + + XPRINTK("nicstar%d: writing %d tst entries.\n", + card->index, n); + for (frscdi = 0; frscdi < NS_FRSCD_NUM; frscdi++) { + if (card->scd2vc[frscdi] == NULL) { + card->scd2vc[frscdi] = vc; + break; + } + } + if (frscdi == NS_FRSCD_NUM) { + PRINTK + ("nicstar%d: no SCD available for CBR channel.\n", + card->index); + card->tst_free_entries += n; + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + return -EBUSY; + } + + vc->cbr_scd = NS_FRSCD + frscdi * NS_FRSCD_SIZE; + + scq = get_scq(card, CBR_SCQSIZE, vc->cbr_scd); + if (scq == NULL) { + PRINTK("nicstar%d: can't get fixed rate SCQ.\n", + card->index); + card->scd2vc[frscdi] = NULL; + card->tst_free_entries += n; + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + return -ENOMEM; + } + vc->scq = scq; + u32d[0] = scq_virt_to_bus(scq, scq->base); + u32d[1] = (u32) 0x00000000; + u32d[2] = (u32) 0xffffffff; + u32d[3] = (u32) 0x00000000; + ns_write_sram(card, vc->cbr_scd, u32d, 4); + + fill_tst(card, n, vc); + } else if (vcc->qos.txtp.traffic_class == ATM_UBR) { + vc->cbr_scd = 0x00000000; + vc->scq = card->scq0; + } + + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + vc->tx = 1; + vc->tx_vcc = vcc; + vc->tbd_count = 0; + } + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + u32 status; + + vc->rx = 1; + vc->rx_vcc = vcc; + vc->rx_iov = NULL; + + /* Open the connection in hardware */ + if (vcc->qos.aal == ATM_AAL5) + status = NS_RCTE_AAL5 | NS_RCTE_CONNECTOPEN; + else /* vcc->qos.aal == ATM_AAL0 */ + status = NS_RCTE_AAL0 | NS_RCTE_CONNECTOPEN; +#ifdef RCQ_SUPPORT + status |= NS_RCTE_RAWCELLINTEN; +#endif /* RCQ_SUPPORT */ + ns_write_sram(card, + NS_RCT + + (vpi << card->vcibits | vci) * + NS_RCT_ENTRY_SIZE, &status, 1); + } + + } + + set_bit(ATM_VF_READY, &vcc->flags); + return 0; +} + +static void ns_close(struct atm_vcc *vcc) +{ + vc_map *vc; + ns_dev *card; + u32 data; + int i; + + vc = vcc->dev_data; + card = vcc->dev->dev_data; + PRINTK("nicstar%d: closing vpi.vci %d.%d \n", card->index, + (int)vcc->vpi, vcc->vci); + + clear_bit(ATM_VF_READY, &vcc->flags); + + if (vcc->qos.rxtp.traffic_class != ATM_NONE) { + u32 addr; + unsigned long flags; + + addr = + NS_RCT + + (vcc->vpi << card->vcibits | vcc->vci) * NS_RCT_ENTRY_SIZE; + spin_lock_irqsave(&card->res_lock, flags); + while (CMD_BUSY(card)) ; + writel(NS_CMD_CLOSE_CONNECTION | addr << 2, + card->membase + CMD); + spin_unlock_irqrestore(&card->res_lock, flags); + + vc->rx = 0; + if (vc->rx_iov != NULL) { + struct sk_buff *iovb; + u32 stat; + + stat = readl(card->membase + STAT); + card->sbfqc = ns_stat_sfbqc_get(stat); + card->lbfqc = ns_stat_lfbqc_get(stat); + + PRINTK + ("nicstar%d: closing a VC with pending rx buffers.\n", + card->index); + iovb = vc->rx_iov; + recycle_iovec_rx_bufs(card, (struct iovec *)iovb->data, + NS_PRV_IOVCNT(iovb)); + NS_PRV_IOVCNT(iovb) = 0; + spin_lock_irqsave(&card->int_lock, flags); + recycle_iov_buf(card, iovb); + spin_unlock_irqrestore(&card->int_lock, flags); + vc->rx_iov = NULL; + } + } + + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + vc->tx = 0; + } + + if (vcc->qos.txtp.traffic_class == ATM_CBR) { + unsigned long flags; + ns_scqe *scqep; + scq_info *scq; + + scq = vc->scq; + + for (;;) { + spin_lock_irqsave(&scq->lock, flags); + scqep = scq->next; + if (scqep == scq->base) + scqep = scq->last; + else + scqep--; + if (scqep == scq->tail) { + spin_unlock_irqrestore(&scq->lock, flags); + break; + } + /* If the last entry is not a TSR, place one in the SCQ in order to + be able to completely drain it and then close. */ + if (!ns_scqe_is_tsr(scqep) && scq->tail != scq->next) { + ns_scqe tsr; + u32 scdi, scqi; + u32 data; + int index; + + tsr.word_1 = ns_tsr_mkword_1(NS_TSR_INTENABLE); + scdi = (vc->cbr_scd - NS_FRSCD) / NS_FRSCD_SIZE; + scqi = scq->next - scq->base; + tsr.word_2 = ns_tsr_mkword_2(scdi, scqi); + tsr.word_3 = 0x00000000; + tsr.word_4 = 0x00000000; + *scq->next = tsr; + index = (int)scqi; + scq->skb[index] = NULL; + if (scq->next == scq->last) + scq->next = scq->base; + else + scq->next++; + data = scq_virt_to_bus(scq, scq->next); + ns_write_sram(card, scq->scd, &data, 1); + } + spin_unlock_irqrestore(&scq->lock, flags); + schedule(); + } + + /* Free all TST entries */ + data = NS_TST_OPCODE_VARIABLE; + for (i = 0; i < NS_TST_NUM_ENTRIES; i++) { + if (card->tste2vc[i] == vc) { + ns_write_sram(card, card->tst_addr + i, &data, + 1); + card->tste2vc[i] = NULL; + card->tst_free_entries++; + } + } + + card->scd2vc[(vc->cbr_scd - NS_FRSCD) / NS_FRSCD_SIZE] = NULL; + free_scq(card, vc->scq, vcc); + } + + /* remove all references to vcc before deleting it */ + if (vcc->qos.txtp.traffic_class != ATM_NONE) { + unsigned long flags; + scq_info *scq = card->scq0; + + spin_lock_irqsave(&scq->lock, flags); + + for (i = 0; i < scq->num_entries; i++) { + if (scq->skb[i] && ATM_SKB(scq->skb[i])->vcc == vcc) { + ATM_SKB(scq->skb[i])->vcc = NULL; + atm_return(vcc, scq->skb[i]->truesize); + PRINTK + ("nicstar: deleted pending vcc mapping\n"); + } + } + + spin_unlock_irqrestore(&scq->lock, flags); + } + + vcc->dev_data = NULL; + clear_bit(ATM_VF_PARTIAL, &vcc->flags); + clear_bit(ATM_VF_ADDR, &vcc->flags); + +#ifdef RX_DEBUG + { + u32 stat, cfg; + stat = readl(card->membase + STAT); + cfg = readl(card->membase + CFG); + printk("STAT = 0x%08X CFG = 0x%08X \n", stat, cfg); + printk + ("TSQ: base = 0x%p next = 0x%p last = 0x%p TSQT = 0x%08X \n", + card->tsq.base, card->tsq.next, + card->tsq.last, readl(card->membase + TSQT)); + printk + ("RSQ: base = 0x%p next = 0x%p last = 0x%p RSQT = 0x%08X \n", + card->rsq.base, card->rsq.next, + card->rsq.last, readl(card->membase + RSQT)); + printk("Empty free buffer queue interrupt %s \n", + card->efbie ? "enabled" : "disabled"); + printk("SBCNT = %d count = %d LBCNT = %d count = %d \n", + ns_stat_sfbqc_get(stat), card->sbpool.count, + ns_stat_lfbqc_get(stat), card->lbpool.count); + printk("hbpool.count = %d iovpool.count = %d \n", + card->hbpool.count, card->iovpool.count); + } +#endif /* RX_DEBUG */ +} + +static void fill_tst(ns_dev * card, int n, vc_map * vc) +{ + u32 new_tst; + unsigned long cl; + int e, r; + u32 data; + + /* It would be very complicated to keep the two TSTs synchronized while + assuring that writes are only made to the inactive TST. So, for now I + will use only one TST. If problems occur, I will change this again */ + + new_tst = card->tst_addr; + + /* Fill procedure */ + + for (e = 0; e < NS_TST_NUM_ENTRIES; e++) { + if (card->tste2vc[e] == NULL) + break; + } + if (e == NS_TST_NUM_ENTRIES) { + printk("nicstar%d: No free TST entries found. \n", card->index); + return; + } + + r = n; + cl = NS_TST_NUM_ENTRIES; + data = ns_tste_make(NS_TST_OPCODE_FIXED, vc->cbr_scd); + + while (r > 0) { + if (cl >= NS_TST_NUM_ENTRIES && card->tste2vc[e] == NULL) { + card->tste2vc[e] = vc; + ns_write_sram(card, new_tst + e, &data, 1); + cl -= NS_TST_NUM_ENTRIES; + r--; + } + + if (++e == NS_TST_NUM_ENTRIES) { + e = 0; + } + cl += n; + } + + /* End of fill procedure */ + + data = ns_tste_make(NS_TST_OPCODE_END, new_tst); + ns_write_sram(card, new_tst + NS_TST_NUM_ENTRIES, &data, 1); + ns_write_sram(card, card->tst_addr + NS_TST_NUM_ENTRIES, &data, 1); + card->tst_addr = new_tst; +} + +static int ns_send(struct atm_vcc *vcc, struct sk_buff *skb) +{ + ns_dev *card; + vc_map *vc; + scq_info *scq; + unsigned long buflen; + ns_scqe scqe; + u32 flags; /* TBD flags, not CPU flags */ + + card = vcc->dev->dev_data; + TXPRINTK("nicstar%d: ns_send() called.\n", card->index); + if ((vc = (vc_map *) vcc->dev_data) == NULL) { + printk("nicstar%d: vcc->dev_data == NULL on ns_send().\n", + card->index); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (!vc->tx) { + printk("nicstar%d: Trying to transmit on a non-tx VC.\n", + card->index); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (vcc->qos.aal != ATM_AAL5 && vcc->qos.aal != ATM_AAL0) { + printk("nicstar%d: Only AAL0 and AAL5 are supported.\n", + card->index); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (skb_shinfo(skb)->nr_frags != 0) { + printk("nicstar%d: No scatter-gather yet.\n", card->index); + atomic_inc(&vcc->stats->tx_err); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + ATM_SKB(skb)->vcc = vcc; + + NS_PRV_DMA(skb) = dma_map_single(&card->pcidev->dev, skb->data, + skb->len, DMA_TO_DEVICE); + + if (vcc->qos.aal == ATM_AAL5) { + buflen = (skb->len + 47 + 8) / 48 * 48; /* Multiple of 48 */ + flags = NS_TBD_AAL5; + scqe.word_2 = cpu_to_le32(NS_PRV_DMA(skb)); + scqe.word_3 = cpu_to_le32(skb->len); + scqe.word_4 = + ns_tbd_mkword_4(0, (u32) vcc->vpi, (u32) vcc->vci, 0, + ATM_SKB(skb)-> + atm_options & ATM_ATMOPT_CLP ? 1 : 0); + flags |= NS_TBD_EOPDU; + } else { /* (vcc->qos.aal == ATM_AAL0) */ + + buflen = ATM_CELL_PAYLOAD; /* i.e., 48 bytes */ + flags = NS_TBD_AAL0; + scqe.word_2 = cpu_to_le32(NS_PRV_DMA(skb) + NS_AAL0_HEADER); + scqe.word_3 = cpu_to_le32(0x00000000); + if (*skb->data & 0x02) /* Payload type 1 - end of pdu */ + flags |= NS_TBD_EOPDU; + scqe.word_4 = + cpu_to_le32(*((u32 *) skb->data) & ~NS_TBD_VC_MASK); + /* Force the VPI/VCI to be the same as in VCC struct */ + scqe.word_4 |= + cpu_to_le32((((u32) vcc-> + vpi) << NS_TBD_VPI_SHIFT | ((u32) vcc-> + vci) << + NS_TBD_VCI_SHIFT) & NS_TBD_VC_MASK); + } + + if (vcc->qos.txtp.traffic_class == ATM_CBR) { + scqe.word_1 = ns_tbd_mkword_1_novbr(flags, (u32) buflen); + scq = ((vc_map *) vcc->dev_data)->scq; + } else { + scqe.word_1 = + ns_tbd_mkword_1(flags, (u32) 1, (u32) 1, (u32) buflen); + scq = card->scq0; + } + + if (push_scqe(card, vc, scq, &scqe, skb) != 0) { + atomic_inc(&vcc->stats->tx_err); + dma_unmap_single(&card->pcidev->dev, NS_PRV_DMA(skb), skb->len, + DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + return -EIO; + } + atomic_inc(&vcc->stats->tx); + + return 0; +} + +static int push_scqe(ns_dev * card, vc_map * vc, scq_info * scq, ns_scqe * tbd, + struct sk_buff *skb) +{ + unsigned long flags; + ns_scqe tsr; + u32 scdi, scqi; + int scq_is_vbr; + u32 data; + int index; + + spin_lock_irqsave(&scq->lock, flags); + while (scq->tail == scq->next) { + if (in_interrupt()) { + spin_unlock_irqrestore(&scq->lock, flags); + printk("nicstar%d: Error pushing TBD.\n", card->index); + return 1; + } + + scq->full = 1; + wait_event_interruptible_lock_irq_timeout(scq->scqfull_waitq, + scq->tail != scq->next, + scq->lock, + SCQFULL_TIMEOUT); + + if (scq->full) { + spin_unlock_irqrestore(&scq->lock, flags); + printk("nicstar%d: Timeout pushing TBD.\n", + card->index); + return 1; + } + } + *scq->next = *tbd; + index = (int)(scq->next - scq->base); + scq->skb[index] = skb; + XPRINTK("nicstar%d: sending skb at 0x%p (pos %d).\n", + card->index, skb, index); + XPRINTK("nicstar%d: TBD written:\n0x%x\n0x%x\n0x%x\n0x%x\n at 0x%p.\n", + card->index, le32_to_cpu(tbd->word_1), le32_to_cpu(tbd->word_2), + le32_to_cpu(tbd->word_3), le32_to_cpu(tbd->word_4), + scq->next); + if (scq->next == scq->last) + scq->next = scq->base; + else + scq->next++; + + vc->tbd_count++; + if (scq->num_entries == VBR_SCQ_NUM_ENTRIES) { + scq->tbd_count++; + scq_is_vbr = 1; + } else + scq_is_vbr = 0; + + if (vc->tbd_count >= MAX_TBD_PER_VC + || scq->tbd_count >= MAX_TBD_PER_SCQ) { + int has_run = 0; + + while (scq->tail == scq->next) { + if (in_interrupt()) { + data = scq_virt_to_bus(scq, scq->next); + ns_write_sram(card, scq->scd, &data, 1); + spin_unlock_irqrestore(&scq->lock, flags); + printk("nicstar%d: Error pushing TSR.\n", + card->index); + return 0; + } + + scq->full = 1; + if (has_run++) + break; + wait_event_interruptible_lock_irq_timeout(scq->scqfull_waitq, + scq->tail != scq->next, + scq->lock, + SCQFULL_TIMEOUT); + } + + if (!scq->full) { + tsr.word_1 = ns_tsr_mkword_1(NS_TSR_INTENABLE); + if (scq_is_vbr) + scdi = NS_TSR_SCDISVBR; + else + scdi = (vc->cbr_scd - NS_FRSCD) / NS_FRSCD_SIZE; + scqi = scq->next - scq->base; + tsr.word_2 = ns_tsr_mkword_2(scdi, scqi); + tsr.word_3 = 0x00000000; + tsr.word_4 = 0x00000000; + + *scq->next = tsr; + index = (int)scqi; + scq->skb[index] = NULL; + XPRINTK + ("nicstar%d: TSR written:\n0x%x\n0x%x\n0x%x\n0x%x\n at 0x%p.\n", + card->index, le32_to_cpu(tsr.word_1), + le32_to_cpu(tsr.word_2), le32_to_cpu(tsr.word_3), + le32_to_cpu(tsr.word_4), scq->next); + if (scq->next == scq->last) + scq->next = scq->base; + else + scq->next++; + vc->tbd_count = 0; + scq->tbd_count = 0; + } else + PRINTK("nicstar%d: Timeout pushing TSR.\n", + card->index); + } + data = scq_virt_to_bus(scq, scq->next); + ns_write_sram(card, scq->scd, &data, 1); + + spin_unlock_irqrestore(&scq->lock, flags); + + return 0; +} + +static void process_tsq(ns_dev * card) +{ + u32 scdi; + scq_info *scq; + ns_tsi *previous = NULL, *one_ahead, *two_ahead; + int serviced_entries; /* flag indicating at least on entry was serviced */ + + serviced_entries = 0; + + if (card->tsq.next == card->tsq.last) + one_ahead = card->tsq.base; + else + one_ahead = card->tsq.next + 1; + + if (one_ahead == card->tsq.last) + two_ahead = card->tsq.base; + else + two_ahead = one_ahead + 1; + + while (!ns_tsi_isempty(card->tsq.next) || !ns_tsi_isempty(one_ahead) || + !ns_tsi_isempty(two_ahead)) + /* At most two empty, as stated in the 77201 errata */ + { + serviced_entries = 1; + + /* Skip the one or two possible empty entries */ + while (ns_tsi_isempty(card->tsq.next)) { + if (card->tsq.next == card->tsq.last) + card->tsq.next = card->tsq.base; + else + card->tsq.next++; + } + + if (!ns_tsi_tmrof(card->tsq.next)) { + scdi = ns_tsi_getscdindex(card->tsq.next); + if (scdi == NS_TSI_SCDISVBR) + scq = card->scq0; + else { + if (card->scd2vc[scdi] == NULL) { + printk + ("nicstar%d: could not find VC from SCD index.\n", + card->index); + ns_tsi_init(card->tsq.next); + return; + } + scq = card->scd2vc[scdi]->scq; + } + drain_scq(card, scq, ns_tsi_getscqpos(card->tsq.next)); + scq->full = 0; + wake_up_interruptible(&(scq->scqfull_waitq)); + } + + ns_tsi_init(card->tsq.next); + previous = card->tsq.next; + if (card->tsq.next == card->tsq.last) + card->tsq.next = card->tsq.base; + else + card->tsq.next++; + + if (card->tsq.next == card->tsq.last) + one_ahead = card->tsq.base; + else + one_ahead = card->tsq.next + 1; + + if (one_ahead == card->tsq.last) + two_ahead = card->tsq.base; + else + two_ahead = one_ahead + 1; + } + + if (serviced_entries) + writel(PTR_DIFF(previous, card->tsq.base), + card->membase + TSQH); +} + +static void drain_scq(ns_dev * card, scq_info * scq, int pos) +{ + struct atm_vcc *vcc; + struct sk_buff *skb; + int i; + unsigned long flags; + + XPRINTK("nicstar%d: drain_scq() called, scq at 0x%p, pos %d.\n", + card->index, scq, pos); + if (pos >= scq->num_entries) { + printk("nicstar%d: Bad index on drain_scq().\n", card->index); + return; + } + + spin_lock_irqsave(&scq->lock, flags); + i = (int)(scq->tail - scq->base); + if (++i == scq->num_entries) + i = 0; + while (i != pos) { + skb = scq->skb[i]; + XPRINTK("nicstar%d: freeing skb at 0x%p (index %d).\n", + card->index, skb, i); + if (skb != NULL) { + dma_unmap_single(&card->pcidev->dev, + NS_PRV_DMA(skb), + skb->len, + DMA_TO_DEVICE); + vcc = ATM_SKB(skb)->vcc; + if (vcc && vcc->pop != NULL) { + vcc->pop(vcc, skb); + } else { + dev_kfree_skb_irq(skb); + } + scq->skb[i] = NULL; + } + if (++i == scq->num_entries) + i = 0; + } + scq->tail = scq->base + pos; + spin_unlock_irqrestore(&scq->lock, flags); +} + +static void process_rsq(ns_dev * card) +{ + ns_rsqe *previous; + + if (!ns_rsqe_valid(card->rsq.next)) + return; + do { + dequeue_rx(card, card->rsq.next); + ns_rsqe_init(card->rsq.next); + previous = card->rsq.next; + if (card->rsq.next == card->rsq.last) + card->rsq.next = card->rsq.base; + else + card->rsq.next++; + } while (ns_rsqe_valid(card->rsq.next)); + writel(PTR_DIFF(previous, card->rsq.base), card->membase + RSQH); +} + +static void dequeue_rx(ns_dev * card, ns_rsqe * rsqe) +{ + u32 vpi, vci; + vc_map *vc; + struct sk_buff *iovb; + struct iovec *iov; + struct atm_vcc *vcc; + struct sk_buff *skb; + unsigned short aal5_len; + int len; + u32 stat; + u32 id; + + stat = readl(card->membase + STAT); + card->sbfqc = ns_stat_sfbqc_get(stat); + card->lbfqc = ns_stat_lfbqc_get(stat); + + id = le32_to_cpu(rsqe->buffer_handle); + skb = idr_remove(&card->idr, id); + if (!skb) { + RXPRINTK(KERN_ERR + "nicstar%d: skb not found!\n", card->index); + return; + } + dma_sync_single_for_cpu(&card->pcidev->dev, + NS_PRV_DMA(skb), + (NS_PRV_BUFTYPE(skb) == BUF_SM + ? NS_SMSKBSIZE : NS_LGSKBSIZE), + DMA_FROM_DEVICE); + dma_unmap_single(&card->pcidev->dev, + NS_PRV_DMA(skb), + (NS_PRV_BUFTYPE(skb) == BUF_SM + ? NS_SMSKBSIZE : NS_LGSKBSIZE), + DMA_FROM_DEVICE); + vpi = ns_rsqe_vpi(rsqe); + vci = ns_rsqe_vci(rsqe); + if (vpi >= 1UL << card->vpibits || vci >= 1UL << card->vcibits) { + printk("nicstar%d: SDU received for out-of-range vc %d.%d.\n", + card->index, vpi, vci); + recycle_rx_buf(card, skb); + return; + } + + vc = &(card->vcmap[vpi << card->vcibits | vci]); + if (!vc->rx) { + RXPRINTK("nicstar%d: SDU received on non-rx vc %d.%d.\n", + card->index, vpi, vci); + recycle_rx_buf(card, skb); + return; + } + + vcc = vc->rx_vcc; + + if (vcc->qos.aal == ATM_AAL0) { + struct sk_buff *sb; + unsigned char *cell; + int i; + + cell = skb->data; + for (i = ns_rsqe_cellcount(rsqe); i; i--) { + sb = dev_alloc_skb(NS_SMSKBSIZE); + if (!sb) { + printk + ("nicstar%d: Can't allocate buffers for aal0.\n", + card->index); + atomic_add(i, &vcc->stats->rx_drop); + break; + } + if (!atm_charge(vcc, sb->truesize)) { + RXPRINTK + ("nicstar%d: atm_charge() dropped aal0 packets.\n", + card->index); + atomic_add(i - 1, &vcc->stats->rx_drop); /* already increased by 1 */ + dev_kfree_skb_any(sb); + break; + } + /* Rebuild the header */ + *((u32 *) sb->data) = le32_to_cpu(rsqe->word_1) << 4 | + (ns_rsqe_clp(rsqe) ? 0x00000001 : 0x00000000); + if (i == 1 && ns_rsqe_eopdu(rsqe)) + *((u32 *) sb->data) |= 0x00000002; + skb_put(sb, NS_AAL0_HEADER); + memcpy(skb_tail_pointer(sb), cell, ATM_CELL_PAYLOAD); + skb_put(sb, ATM_CELL_PAYLOAD); + ATM_SKB(sb)->vcc = vcc; + __net_timestamp(sb); + vcc->push(vcc, sb); + atomic_inc(&vcc->stats->rx); + cell += ATM_CELL_PAYLOAD; + } + + recycle_rx_buf(card, skb); + return; + } + + /* To reach this point, the AAL layer can only be AAL5 */ + + if ((iovb = vc->rx_iov) == NULL) { + iovb = skb_dequeue(&(card->iovpool.queue)); + if (iovb == NULL) { /* No buffers in the queue */ + iovb = alloc_skb(NS_IOVBUFSIZE, GFP_ATOMIC); + if (iovb == NULL) { + printk("nicstar%d: Out of iovec buffers.\n", + card->index); + atomic_inc(&vcc->stats->rx_drop); + recycle_rx_buf(card, skb); + return; + } + NS_PRV_BUFTYPE(iovb) = BUF_NONE; + } else if (--card->iovpool.count < card->iovnr.min) { + struct sk_buff *new_iovb; + if ((new_iovb = + alloc_skb(NS_IOVBUFSIZE, GFP_ATOMIC)) != NULL) { + NS_PRV_BUFTYPE(iovb) = BUF_NONE; + skb_queue_tail(&card->iovpool.queue, new_iovb); + card->iovpool.count++; + } + } + vc->rx_iov = iovb; + NS_PRV_IOVCNT(iovb) = 0; + iovb->len = 0; + iovb->data = iovb->head; + skb_reset_tail_pointer(iovb); + /* IMPORTANT: a pointer to the sk_buff containing the small or large + buffer is stored as iovec base, NOT a pointer to the + small or large buffer itself. */ + } else if (NS_PRV_IOVCNT(iovb) >= NS_MAX_IOVECS) { + printk("nicstar%d: received too big AAL5 SDU.\n", card->index); + atomic_inc(&vcc->stats->rx_err); + recycle_iovec_rx_bufs(card, (struct iovec *)iovb->data, + NS_MAX_IOVECS); + NS_PRV_IOVCNT(iovb) = 0; + iovb->len = 0; + iovb->data = iovb->head; + skb_reset_tail_pointer(iovb); + } + iov = &((struct iovec *)iovb->data)[NS_PRV_IOVCNT(iovb)++]; + iov->iov_base = (void *)skb; + iov->iov_len = ns_rsqe_cellcount(rsqe) * 48; + iovb->len += iov->iov_len; + +#ifdef EXTRA_DEBUG + if (NS_PRV_IOVCNT(iovb) == 1) { + if (NS_PRV_BUFTYPE(skb) != BUF_SM) { + printk + ("nicstar%d: Expected a small buffer, and this is not one.\n", + card->index); + which_list(card, skb); + atomic_inc(&vcc->stats->rx_err); + recycle_rx_buf(card, skb); + vc->rx_iov = NULL; + recycle_iov_buf(card, iovb); + return; + } + } else { /* NS_PRV_IOVCNT(iovb) >= 2 */ + + if (NS_PRV_BUFTYPE(skb) != BUF_LG) { + printk + ("nicstar%d: Expected a large buffer, and this is not one.\n", + card->index); + which_list(card, skb); + atomic_inc(&vcc->stats->rx_err); + recycle_iovec_rx_bufs(card, (struct iovec *)iovb->data, + NS_PRV_IOVCNT(iovb)); + vc->rx_iov = NULL; + recycle_iov_buf(card, iovb); + return; + } + } +#endif /* EXTRA_DEBUG */ + + if (ns_rsqe_eopdu(rsqe)) { + /* This works correctly regardless of the endianness of the host */ + unsigned char *L1L2 = (unsigned char *) + (skb->data + iov->iov_len - 6); + aal5_len = L1L2[0] << 8 | L1L2[1]; + len = (aal5_len == 0x0000) ? 0x10000 : aal5_len; + if (ns_rsqe_crcerr(rsqe) || + len + 8 > iovb->len || len + (47 + 8) < iovb->len) { + printk("nicstar%d: AAL5 CRC error", card->index); + if (len + 8 > iovb->len || len + (47 + 8) < iovb->len) + printk(" - PDU size mismatch.\n"); + else + printk(".\n"); + atomic_inc(&vcc->stats->rx_err); + recycle_iovec_rx_bufs(card, (struct iovec *)iovb->data, + NS_PRV_IOVCNT(iovb)); + vc->rx_iov = NULL; + recycle_iov_buf(card, iovb); + return; + } + + /* By this point we (hopefully) have a complete SDU without errors. */ + + if (NS_PRV_IOVCNT(iovb) == 1) { /* Just a small buffer */ + /* skb points to a small buffer */ + if (!atm_charge(vcc, skb->truesize)) { + push_rxbufs(card, skb); + atomic_inc(&vcc->stats->rx_drop); + } else { + skb_put(skb, len); + dequeue_sm_buf(card, skb); + ATM_SKB(skb)->vcc = vcc; + __net_timestamp(skb); + vcc->push(vcc, skb); + atomic_inc(&vcc->stats->rx); + } + } else if (NS_PRV_IOVCNT(iovb) == 2) { /* One small plus one large buffer */ + struct sk_buff *sb; + + sb = (struct sk_buff *)(iov - 1)->iov_base; + /* skb points to a large buffer */ + + if (len <= NS_SMBUFSIZE) { + if (!atm_charge(vcc, sb->truesize)) { + push_rxbufs(card, sb); + atomic_inc(&vcc->stats->rx_drop); + } else { + skb_put(sb, len); + dequeue_sm_buf(card, sb); + ATM_SKB(sb)->vcc = vcc; + __net_timestamp(sb); + vcc->push(vcc, sb); + atomic_inc(&vcc->stats->rx); + } + + push_rxbufs(card, skb); + + } else { /* len > NS_SMBUFSIZE, the usual case */ + + if (!atm_charge(vcc, skb->truesize)) { + push_rxbufs(card, skb); + atomic_inc(&vcc->stats->rx_drop); + } else { + dequeue_lg_buf(card, skb); + skb_push(skb, NS_SMBUFSIZE); + skb_copy_from_linear_data(sb, skb->data, + NS_SMBUFSIZE); + skb_put(skb, len - NS_SMBUFSIZE); + ATM_SKB(skb)->vcc = vcc; + __net_timestamp(skb); + vcc->push(vcc, skb); + atomic_inc(&vcc->stats->rx); + } + + push_rxbufs(card, sb); + + } + + } else { /* Must push a huge buffer */ + + struct sk_buff *hb, *sb, *lb; + int remaining, tocopy; + int j; + + hb = skb_dequeue(&(card->hbpool.queue)); + if (hb == NULL) { /* No buffers in the queue */ + + hb = dev_alloc_skb(NS_HBUFSIZE); + if (hb == NULL) { + printk + ("nicstar%d: Out of huge buffers.\n", + card->index); + atomic_inc(&vcc->stats->rx_drop); + recycle_iovec_rx_bufs(card, + (struct iovec *) + iovb->data, + NS_PRV_IOVCNT(iovb)); + vc->rx_iov = NULL; + recycle_iov_buf(card, iovb); + return; + } else if (card->hbpool.count < card->hbnr.min) { + struct sk_buff *new_hb; + if ((new_hb = + dev_alloc_skb(NS_HBUFSIZE)) != + NULL) { + skb_queue_tail(&card->hbpool. + queue, new_hb); + card->hbpool.count++; + } + } + NS_PRV_BUFTYPE(hb) = BUF_NONE; + } else if (--card->hbpool.count < card->hbnr.min) { + struct sk_buff *new_hb; + if ((new_hb = + dev_alloc_skb(NS_HBUFSIZE)) != NULL) { + NS_PRV_BUFTYPE(new_hb) = BUF_NONE; + skb_queue_tail(&card->hbpool.queue, + new_hb); + card->hbpool.count++; + } + if (card->hbpool.count < card->hbnr.min) { + if ((new_hb = + dev_alloc_skb(NS_HBUFSIZE)) != + NULL) { + NS_PRV_BUFTYPE(new_hb) = + BUF_NONE; + skb_queue_tail(&card->hbpool. + queue, new_hb); + card->hbpool.count++; + } + } + } + + iov = (struct iovec *)iovb->data; + + if (!atm_charge(vcc, hb->truesize)) { + recycle_iovec_rx_bufs(card, iov, + NS_PRV_IOVCNT(iovb)); + if (card->hbpool.count < card->hbnr.max) { + skb_queue_tail(&card->hbpool.queue, hb); + card->hbpool.count++; + } else + dev_kfree_skb_any(hb); + atomic_inc(&vcc->stats->rx_drop); + } else { + /* Copy the small buffer to the huge buffer */ + sb = (struct sk_buff *)iov->iov_base; + skb_copy_from_linear_data(sb, hb->data, + iov->iov_len); + skb_put(hb, iov->iov_len); + remaining = len - iov->iov_len; + iov++; + /* Free the small buffer */ + push_rxbufs(card, sb); + + /* Copy all large buffers to the huge buffer and free them */ + for (j = 1; j < NS_PRV_IOVCNT(iovb); j++) { + lb = (struct sk_buff *)iov->iov_base; + tocopy = + min_t(int, remaining, iov->iov_len); + skb_copy_from_linear_data(lb, + skb_tail_pointer + (hb), tocopy); + skb_put(hb, tocopy); + iov++; + remaining -= tocopy; + push_rxbufs(card, lb); + } +#ifdef EXTRA_DEBUG + if (remaining != 0 || hb->len != len) + printk + ("nicstar%d: Huge buffer len mismatch.\n", + card->index); +#endif /* EXTRA_DEBUG */ + ATM_SKB(hb)->vcc = vcc; + __net_timestamp(hb); + vcc->push(vcc, hb); + atomic_inc(&vcc->stats->rx); + } + } + + vc->rx_iov = NULL; + recycle_iov_buf(card, iovb); + } + +} + +static void recycle_rx_buf(ns_dev * card, struct sk_buff *skb) +{ + if (unlikely(NS_PRV_BUFTYPE(skb) == BUF_NONE)) { + printk("nicstar%d: What kind of rx buffer is this?\n", + card->index); + dev_kfree_skb_any(skb); + } else + push_rxbufs(card, skb); +} + +static void recycle_iovec_rx_bufs(ns_dev * card, struct iovec *iov, int count) +{ + while (count-- > 0) + recycle_rx_buf(card, (struct sk_buff *)(iov++)->iov_base); +} + +static void recycle_iov_buf(ns_dev * card, struct sk_buff *iovb) +{ + if (card->iovpool.count < card->iovnr.max) { + skb_queue_tail(&card->iovpool.queue, iovb); + card->iovpool.count++; + } else + dev_kfree_skb_any(iovb); +} + +static void dequeue_sm_buf(ns_dev * card, struct sk_buff *sb) +{ + skb_unlink(sb, &card->sbpool.queue); + if (card->sbfqc < card->sbnr.init) { + struct sk_buff *new_sb; + if ((new_sb = dev_alloc_skb(NS_SMSKBSIZE)) != NULL) { + NS_PRV_BUFTYPE(new_sb) = BUF_SM; + skb_queue_tail(&card->sbpool.queue, new_sb); + skb_reserve(new_sb, NS_AAL0_HEADER); + push_rxbufs(card, new_sb); + } + } + if (card->sbfqc < card->sbnr.init) + { + struct sk_buff *new_sb; + if ((new_sb = dev_alloc_skb(NS_SMSKBSIZE)) != NULL) { + NS_PRV_BUFTYPE(new_sb) = BUF_SM; + skb_queue_tail(&card->sbpool.queue, new_sb); + skb_reserve(new_sb, NS_AAL0_HEADER); + push_rxbufs(card, new_sb); + } + } +} + +static void dequeue_lg_buf(ns_dev * card, struct sk_buff *lb) +{ + skb_unlink(lb, &card->lbpool.queue); + if (card->lbfqc < card->lbnr.init) { + struct sk_buff *new_lb; + if ((new_lb = dev_alloc_skb(NS_LGSKBSIZE)) != NULL) { + NS_PRV_BUFTYPE(new_lb) = BUF_LG; + skb_queue_tail(&card->lbpool.queue, new_lb); + skb_reserve(new_lb, NS_SMBUFSIZE); + push_rxbufs(card, new_lb); + } + } + if (card->lbfqc < card->lbnr.init) + { + struct sk_buff *new_lb; + if ((new_lb = dev_alloc_skb(NS_LGSKBSIZE)) != NULL) { + NS_PRV_BUFTYPE(new_lb) = BUF_LG; + skb_queue_tail(&card->lbpool.queue, new_lb); + skb_reserve(new_lb, NS_SMBUFSIZE); + push_rxbufs(card, new_lb); + } + } +} + +static int ns_proc_read(struct atm_dev *dev, loff_t * pos, char *page) +{ + u32 stat; + ns_dev *card; + int left; + + left = (int)*pos; + card = (ns_dev *) dev->dev_data; + stat = readl(card->membase + STAT); + if (!left--) + return sprintf(page, "Pool count min init max \n"); + if (!left--) + return sprintf(page, "Small %5d %5d %5d %5d \n", + ns_stat_sfbqc_get(stat), card->sbnr.min, + card->sbnr.init, card->sbnr.max); + if (!left--) + return sprintf(page, "Large %5d %5d %5d %5d \n", + ns_stat_lfbqc_get(stat), card->lbnr.min, + card->lbnr.init, card->lbnr.max); + if (!left--) + return sprintf(page, "Huge %5d %5d %5d %5d \n", + card->hbpool.count, card->hbnr.min, + card->hbnr.init, card->hbnr.max); + if (!left--) + return sprintf(page, "Iovec %5d %5d %5d %5d \n", + card->iovpool.count, card->iovnr.min, + card->iovnr.init, card->iovnr.max); + if (!left--) { + int retval; + retval = + sprintf(page, "Interrupt counter: %u \n", card->intcnt); + card->intcnt = 0; + return retval; + } +#if 0 + /* Dump 25.6 Mbps PHY registers */ + /* Now there's a 25.6 Mbps PHY driver this code isn't needed. I left it + here just in case it's needed for debugging. */ + if (card->max_pcr == ATM_25_PCR && !left--) { + u32 phy_regs[4]; + u32 i; + + for (i = 0; i < 4; i++) { + while (CMD_BUSY(card)) ; + writel(NS_CMD_READ_UTILITY | 0x00000200 | i, + card->membase + CMD); + while (CMD_BUSY(card)) ; + phy_regs[i] = readl(card->membase + DR0) & 0x000000FF; + } + + return sprintf(page, "PHY regs: 0x%02X 0x%02X 0x%02X 0x%02X \n", + phy_regs[0], phy_regs[1], phy_regs[2], + phy_regs[3]); + } +#endif /* 0 - Dump 25.6 Mbps PHY registers */ +#if 0 + /* Dump TST */ + if (left-- < NS_TST_NUM_ENTRIES) { + if (card->tste2vc[left + 1] == NULL) + return sprintf(page, "%5d - VBR/UBR \n", left + 1); + else + return sprintf(page, "%5d - %d %d \n", left + 1, + card->tste2vc[left + 1]->tx_vcc->vpi, + card->tste2vc[left + 1]->tx_vcc->vci); + } +#endif /* 0 */ + return 0; +} + +static int ns_ioctl(struct atm_dev *dev, unsigned int cmd, void __user * arg) +{ + ns_dev *card; + pool_levels pl; + long btype; + unsigned long flags; + + card = dev->dev_data; + switch (cmd) { + case NS_GETPSTAT: + if (get_user + (pl.buftype, &((pool_levels __user *) arg)->buftype)) + return -EFAULT; + switch (pl.buftype) { + case NS_BUFTYPE_SMALL: + pl.count = + ns_stat_sfbqc_get(readl(card->membase + STAT)); + pl.level.min = card->sbnr.min; + pl.level.init = card->sbnr.init; + pl.level.max = card->sbnr.max; + break; + + case NS_BUFTYPE_LARGE: + pl.count = + ns_stat_lfbqc_get(readl(card->membase + STAT)); + pl.level.min = card->lbnr.min; + pl.level.init = card->lbnr.init; + pl.level.max = card->lbnr.max; + break; + + case NS_BUFTYPE_HUGE: + pl.count = card->hbpool.count; + pl.level.min = card->hbnr.min; + pl.level.init = card->hbnr.init; + pl.level.max = card->hbnr.max; + break; + + case NS_BUFTYPE_IOVEC: + pl.count = card->iovpool.count; + pl.level.min = card->iovnr.min; + pl.level.init = card->iovnr.init; + pl.level.max = card->iovnr.max; + break; + + default: + return -ENOIOCTLCMD; + + } + if (!copy_to_user((pool_levels __user *) arg, &pl, sizeof(pl))) + return (sizeof(pl)); + else + return -EFAULT; + + case NS_SETBUFLEV: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + if (copy_from_user(&pl, (pool_levels __user *) arg, sizeof(pl))) + return -EFAULT; + if (pl.level.min >= pl.level.init + || pl.level.init >= pl.level.max) + return -EINVAL; + if (pl.level.min == 0) + return -EINVAL; + switch (pl.buftype) { + case NS_BUFTYPE_SMALL: + if (pl.level.max > TOP_SB) + return -EINVAL; + card->sbnr.min = pl.level.min; + card->sbnr.init = pl.level.init; + card->sbnr.max = pl.level.max; + break; + + case NS_BUFTYPE_LARGE: + if (pl.level.max > TOP_LB) + return -EINVAL; + card->lbnr.min = pl.level.min; + card->lbnr.init = pl.level.init; + card->lbnr.max = pl.level.max; + break; + + case NS_BUFTYPE_HUGE: + if (pl.level.max > TOP_HB) + return -EINVAL; + card->hbnr.min = pl.level.min; + card->hbnr.init = pl.level.init; + card->hbnr.max = pl.level.max; + break; + + case NS_BUFTYPE_IOVEC: + if (pl.level.max > TOP_IOVB) + return -EINVAL; + card->iovnr.min = pl.level.min; + card->iovnr.init = pl.level.init; + card->iovnr.max = pl.level.max; + break; + + default: + return -EINVAL; + + } + return 0; + + case NS_ADJBUFLEV: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + btype = (long)arg; /* a long is the same size as a pointer or bigger */ + switch (btype) { + case NS_BUFTYPE_SMALL: + while (card->sbfqc < card->sbnr.init) { + struct sk_buff *sb; + + sb = __dev_alloc_skb(NS_SMSKBSIZE, GFP_KERNEL); + if (sb == NULL) + return -ENOMEM; + NS_PRV_BUFTYPE(sb) = BUF_SM; + skb_queue_tail(&card->sbpool.queue, sb); + skb_reserve(sb, NS_AAL0_HEADER); + push_rxbufs(card, sb); + } + break; + + case NS_BUFTYPE_LARGE: + while (card->lbfqc < card->lbnr.init) { + struct sk_buff *lb; + + lb = __dev_alloc_skb(NS_LGSKBSIZE, GFP_KERNEL); + if (lb == NULL) + return -ENOMEM; + NS_PRV_BUFTYPE(lb) = BUF_LG; + skb_queue_tail(&card->lbpool.queue, lb); + skb_reserve(lb, NS_SMBUFSIZE); + push_rxbufs(card, lb); + } + break; + + case NS_BUFTYPE_HUGE: + while (card->hbpool.count > card->hbnr.init) { + struct sk_buff *hb; + + spin_lock_irqsave(&card->int_lock, flags); + hb = skb_dequeue(&card->hbpool.queue); + card->hbpool.count--; + spin_unlock_irqrestore(&card->int_lock, flags); + if (hb == NULL) + printk + ("nicstar%d: huge buffer count inconsistent.\n", + card->index); + else + dev_kfree_skb_any(hb); + + } + while (card->hbpool.count < card->hbnr.init) { + struct sk_buff *hb; + + hb = __dev_alloc_skb(NS_HBUFSIZE, GFP_KERNEL); + if (hb == NULL) + return -ENOMEM; + NS_PRV_BUFTYPE(hb) = BUF_NONE; + spin_lock_irqsave(&card->int_lock, flags); + skb_queue_tail(&card->hbpool.queue, hb); + card->hbpool.count++; + spin_unlock_irqrestore(&card->int_lock, flags); + } + break; + + case NS_BUFTYPE_IOVEC: + while (card->iovpool.count > card->iovnr.init) { + struct sk_buff *iovb; + + spin_lock_irqsave(&card->int_lock, flags); + iovb = skb_dequeue(&card->iovpool.queue); + card->iovpool.count--; + spin_unlock_irqrestore(&card->int_lock, flags); + if (iovb == NULL) + printk + ("nicstar%d: iovec buffer count inconsistent.\n", + card->index); + else + dev_kfree_skb_any(iovb); + + } + while (card->iovpool.count < card->iovnr.init) { + struct sk_buff *iovb; + + iovb = alloc_skb(NS_IOVBUFSIZE, GFP_KERNEL); + if (iovb == NULL) + return -ENOMEM; + NS_PRV_BUFTYPE(iovb) = BUF_NONE; + spin_lock_irqsave(&card->int_lock, flags); + skb_queue_tail(&card->iovpool.queue, iovb); + card->iovpool.count++; + spin_unlock_irqrestore(&card->int_lock, flags); + } + break; + + default: + return -EINVAL; + + } + return 0; + + default: + if (dev->phy && dev->phy->ioctl) { + return dev->phy->ioctl(dev, cmd, arg); + } else { + printk("nicstar%d: %s == NULL \n", card->index, + dev->phy ? "dev->phy->ioctl" : "dev->phy"); + return -ENOIOCTLCMD; + } + } +} + +#ifdef EXTRA_DEBUG +static void which_list(ns_dev * card, struct sk_buff *skb) +{ + printk("skb buf_type: 0x%08x\n", NS_PRV_BUFTYPE(skb)); +} +#endif /* EXTRA_DEBUG */ + +static void ns_poll(struct timer_list *unused) +{ + int i; + ns_dev *card; + unsigned long flags; + u32 stat_r, stat_w; + + PRINTK("nicstar: Entering ns_poll().\n"); + for (i = 0; i < num_cards; i++) { + card = cards[i]; + if (!spin_trylock_irqsave(&card->int_lock, flags)) { + /* Probably it isn't worth spinning */ + continue; + } + + stat_w = 0; + stat_r = readl(card->membase + STAT); + if (stat_r & NS_STAT_TSIF) + stat_w |= NS_STAT_TSIF; + if (stat_r & NS_STAT_EOPDU) + stat_w |= NS_STAT_EOPDU; + + process_tsq(card); + process_rsq(card); + + writel(stat_w, card->membase + STAT); + spin_unlock_irqrestore(&card->int_lock, flags); + } + mod_timer(&ns_timer, jiffies + NS_POLL_PERIOD); + PRINTK("nicstar: Leaving ns_poll().\n"); +} + +static void ns_phy_put(struct atm_dev *dev, unsigned char value, + unsigned long addr) +{ + ns_dev *card; + unsigned long flags; + + card = dev->dev_data; + spin_lock_irqsave(&card->res_lock, flags); + while (CMD_BUSY(card)) ; + writel((u32) value, card->membase + DR0); + writel(NS_CMD_WRITE_UTILITY | 0x00000200 | (addr & 0x000000FF), + card->membase + CMD); + spin_unlock_irqrestore(&card->res_lock, flags); +} + +static unsigned char ns_phy_get(struct atm_dev *dev, unsigned long addr) +{ + ns_dev *card; + unsigned long flags; + u32 data; + + card = dev->dev_data; + spin_lock_irqsave(&card->res_lock, flags); + while (CMD_BUSY(card)) ; + writel(NS_CMD_READ_UTILITY | 0x00000200 | (addr & 0x000000FF), + card->membase + CMD); + while (CMD_BUSY(card)) ; + data = readl(card->membase + DR0) & 0x000000FF; + spin_unlock_irqrestore(&card->res_lock, flags); + return (unsigned char)data; +} + +module_init(nicstar_init); +module_exit(nicstar_cleanup); diff --git a/drivers/atm/nicstar.h b/drivers/atm/nicstar.h new file mode 100644 index 000000000..1b7f1dfc1 --- /dev/null +++ b/drivers/atm/nicstar.h @@ -0,0 +1,759 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * nicstar.h + * + * Header file for the nicstar device driver. + * + * Author: Rui Prior (rprior@inescn.pt) + * PowerPC support by Jay Talbott (jay_talbott@mcg.mot.com) April 1999 + * + * (C) INESC 1998 + */ + +#ifndef _LINUX_NICSTAR_H_ +#define _LINUX_NICSTAR_H_ + +/* Includes */ + +#include +#include +#include +#include +#include +#include +#include + +/* Options */ + +#define NS_MAX_CARDS 4 /* Maximum number of NICStAR based cards + controlled by the device driver. Must + be <= 5 */ + +#undef RCQ_SUPPORT /* Do not define this for now */ + +#define NS_TST_NUM_ENTRIES 2340 /* + 1 for return */ +#define NS_TST_RESERVED 340 /* N. entries reserved for UBR/ABR/VBR */ + +#define NS_SMBUFSIZE 48 /* 48, 96, 240 or 2048 */ +#define NS_LGBUFSIZE 16384 /* 2048, 4096, 8192 or 16384 */ +#define NS_RSQSIZE 8192 /* 2048, 4096 or 8192 */ +#define NS_VPIBITS 2 /* 0, 1, 2, or 8 */ + +#define NS_MAX_RCTSIZE 4096 /* Number of entries. 4096 or 16384. + Define 4096 only if (all) your card(s) + have 32K x 32bit SRAM, in which case + setting this to 16384 will just waste a + lot of memory. + Setting this to 4096 for a card with + 128K x 32bit SRAM will limit the maximum + VCI. */ + + /*#define NS_PCI_LATENCY 64*//* Must be a multiple of 32 */ + + /* Number of buffers initially allocated */ +#define NUM_SB 32 /* Must be even */ +#define NUM_LB 24 /* Must be even */ +#define NUM_HB 8 /* Pre-allocated huge buffers */ +#define NUM_IOVB 48 /* Iovec buffers */ + + /* Lower level for count of buffers */ +#define MIN_SB 8 /* Must be even */ +#define MIN_LB 8 /* Must be even */ +#define MIN_HB 6 +#define MIN_IOVB 8 + + /* Upper level for count of buffers */ +#define MAX_SB 64 /* Must be even, <= 508 */ +#define MAX_LB 48 /* Must be even, <= 508 */ +#define MAX_HB 10 +#define MAX_IOVB 80 + + /* These are the absolute maximum allowed for the ioctl() */ +#define TOP_SB 256 /* Must be even, <= 508 */ +#define TOP_LB 128 /* Must be even, <= 508 */ +#define TOP_HB 64 +#define TOP_IOVB 256 + +#define MAX_TBD_PER_VC 1 /* Number of TBDs before a TSR */ +#define MAX_TBD_PER_SCQ 10 /* Only meaningful for variable rate SCQs */ + +#undef ENABLE_TSQFIE + +#define SCQFULL_TIMEOUT (5 * HZ) + +#define NS_POLL_PERIOD (HZ) + +#define PCR_TOLERANCE (1.0001) + +/* ESI stuff */ + +#define NICSTAR_EPROM_MAC_ADDR_OFFSET 0x6C +#define NICSTAR_EPROM_MAC_ADDR_OFFSET_ALT 0xF6 + +/* #defines */ + +#define NS_IOREMAP_SIZE 4096 + +/* + * BUF_XX distinguish the Rx buffers depending on their (small/large) size. + * BUG_SM and BUG_LG are both used by the driver and the device. + * BUF_NONE is only used by the driver. + */ +#define BUF_SM 0x00000000 /* These two are used for push_rxbufs() */ +#define BUF_LG 0x00000001 /* CMD, Write_FreeBufQ, LBUF bit */ +#define BUF_NONE 0xffffffff /* Software only: */ + +#define NS_HBUFSIZE 65568 /* Size of max. AAL5 PDU */ +#define NS_MAX_IOVECS (2 + (65568 - NS_SMBUFSIZE) / \ + (NS_LGBUFSIZE - (NS_LGBUFSIZE % 48))) +#define NS_IOVBUFSIZE (NS_MAX_IOVECS * (sizeof(struct iovec))) + +#define NS_SMBUFSIZE_USABLE (NS_SMBUFSIZE - NS_SMBUFSIZE % 48) +#define NS_LGBUFSIZE_USABLE (NS_LGBUFSIZE - NS_LGBUFSIZE % 48) + +#define NS_AAL0_HEADER (ATM_AAL0_SDU - ATM_CELL_PAYLOAD) /* 4 bytes */ + +#define NS_SMSKBSIZE (NS_SMBUFSIZE + NS_AAL0_HEADER) +#define NS_LGSKBSIZE (NS_SMBUFSIZE + NS_LGBUFSIZE) + +/* NICStAR structures located in host memory */ + +/* + * RSQ - Receive Status Queue + * + * Written by the NICStAR, read by the device driver. + */ + +typedef struct ns_rsqe { + u32 word_1; + u32 buffer_handle; + u32 final_aal5_crc32; + u32 word_4; +} ns_rsqe; + +#define ns_rsqe_vpi(ns_rsqep) \ + ((le32_to_cpu((ns_rsqep)->word_1) & 0x00FF0000) >> 16) +#define ns_rsqe_vci(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_1) & 0x0000FFFF) + +#define NS_RSQE_VALID 0x80000000 +#define NS_RSQE_NZGFC 0x00004000 +#define NS_RSQE_EOPDU 0x00002000 +#define NS_RSQE_BUFSIZE 0x00001000 +#define NS_RSQE_CONGESTION 0x00000800 +#define NS_RSQE_CLP 0x00000400 +#define NS_RSQE_CRCERR 0x00000200 + +#define NS_RSQE_BUFSIZE_SM 0x00000000 +#define NS_RSQE_BUFSIZE_LG 0x00001000 + +#define ns_rsqe_valid(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_VALID) +#define ns_rsqe_nzgfc(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_NZGFC) +#define ns_rsqe_eopdu(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_EOPDU) +#define ns_rsqe_bufsize(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_BUFSIZE) +#define ns_rsqe_congestion(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_CONGESTION) +#define ns_rsqe_clp(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_CLP) +#define ns_rsqe_crcerr(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & NS_RSQE_CRCERR) + +#define ns_rsqe_cellcount(ns_rsqep) \ + (le32_to_cpu((ns_rsqep)->word_4) & 0x000001FF) +#define ns_rsqe_init(ns_rsqep) \ + ((ns_rsqep)->word_4 = cpu_to_le32(0x00000000)) + +#define NS_RSQ_NUM_ENTRIES (NS_RSQSIZE / 16) +#define NS_RSQ_ALIGNMENT NS_RSQSIZE + +/* + * RCQ - Raw Cell Queue + * + * Written by the NICStAR, read by the device driver. + */ + +typedef struct cell_payload { + u32 word[12]; +} cell_payload; + +typedef struct ns_rcqe { + u32 word_1; + u32 word_2; + u32 word_3; + u32 word_4; + cell_payload payload; +} ns_rcqe; + +#define NS_RCQE_SIZE 64 /* bytes */ + +#define ns_rcqe_islast(ns_rcqep) \ + (le32_to_cpu((ns_rcqep)->word_2) != 0x00000000) +#define ns_rcqe_cellheader(ns_rcqep) \ + (le32_to_cpu((ns_rcqep)->word_1)) +#define ns_rcqe_nextbufhandle(ns_rcqep) \ + (le32_to_cpu((ns_rcqep)->word_2)) + +/* + * SCQ - Segmentation Channel Queue + * + * Written by the device driver, read by the NICStAR. + */ + +typedef struct ns_scqe { + u32 word_1; + u32 word_2; + u32 word_3; + u32 word_4; +} ns_scqe; + + /* NOTE: SCQ entries can be either a TBD (Transmit Buffer Descriptors) + or TSR (Transmit Status Requests) */ + +#define NS_SCQE_TYPE_TBD 0x00000000 +#define NS_SCQE_TYPE_TSR 0x80000000 + +#define NS_TBD_EOPDU 0x40000000 +#define NS_TBD_AAL0 0x00000000 +#define NS_TBD_AAL34 0x04000000 +#define NS_TBD_AAL5 0x08000000 + +#define NS_TBD_VPI_MASK 0x0FF00000 +#define NS_TBD_VCI_MASK 0x000FFFF0 +#define NS_TBD_VC_MASK (NS_TBD_VPI_MASK | NS_TBD_VCI_MASK) + +#define NS_TBD_VPI_SHIFT 20 +#define NS_TBD_VCI_SHIFT 4 + +#define ns_tbd_mkword_1(flags, m, n, buflen) \ + (cpu_to_le32((flags) | (m) << 23 | (n) << 16 | (buflen))) +#define ns_tbd_mkword_1_novbr(flags, buflen) \ + (cpu_to_le32((flags) | (buflen) | 0x00810000)) +#define ns_tbd_mkword_3(control, pdulen) \ + (cpu_to_le32((control) << 16 | (pdulen))) +#define ns_tbd_mkword_4(gfc, vpi, vci, pt, clp) \ + (cpu_to_le32((gfc) << 28 | (vpi) << 20 | (vci) << 4 | (pt) << 1 | (clp))) + +#define NS_TSR_INTENABLE 0x20000000 + +#define NS_TSR_SCDISVBR 0xFFFF /* Use as scdi for VBR SCD */ + +#define ns_tsr_mkword_1(flags) \ + (cpu_to_le32(NS_SCQE_TYPE_TSR | (flags))) +#define ns_tsr_mkword_2(scdi, scqi) \ + (cpu_to_le32((scdi) << 16 | 0x00008000 | (scqi))) + +#define ns_scqe_is_tsr(ns_scqep) \ + (le32_to_cpu((ns_scqep)->word_1) & NS_SCQE_TYPE_TSR) + +#define VBR_SCQ_NUM_ENTRIES 512 +#define VBR_SCQSIZE 8192 +#define CBR_SCQ_NUM_ENTRIES 64 +#define CBR_SCQSIZE 1024 + +#define NS_SCQE_SIZE 16 + +/* + * TSQ - Transmit Status Queue + * + * Written by the NICStAR, read by the device driver. + */ + +typedef struct ns_tsi { + u32 word_1; + u32 word_2; +} ns_tsi; + + /* NOTE: The first word can be a status word copied from the TSR which + originated the TSI, or a timer overflow indicator. In this last + case, the value of the first word is all zeroes. */ + +#define NS_TSI_EMPTY 0x80000000 +#define NS_TSI_TIMESTAMP_MASK 0x00FFFFFF + +#define ns_tsi_isempty(ns_tsip) \ + (le32_to_cpu((ns_tsip)->word_2) & NS_TSI_EMPTY) +#define ns_tsi_gettimestamp(ns_tsip) \ + (le32_to_cpu((ns_tsip)->word_2) & NS_TSI_TIMESTAMP_MASK) + +#define ns_tsi_init(ns_tsip) \ + ((ns_tsip)->word_2 = cpu_to_le32(NS_TSI_EMPTY)) + +#define NS_TSQSIZE 8192 +#define NS_TSQ_NUM_ENTRIES 1024 +#define NS_TSQ_ALIGNMENT 8192 + +#define NS_TSI_SCDISVBR NS_TSR_SCDISVBR + +#define ns_tsi_tmrof(ns_tsip) \ + (le32_to_cpu((ns_tsip)->word_1) == 0x00000000) +#define ns_tsi_getscdindex(ns_tsip) \ + ((le32_to_cpu((ns_tsip)->word_1) & 0xFFFF0000) >> 16) +#define ns_tsi_getscqpos(ns_tsip) \ + (le32_to_cpu((ns_tsip)->word_1) & 0x00007FFF) + +/* NICStAR structures located in local SRAM */ + +/* + * RCT - Receive Connection Table + * + * Written by both the NICStAR and the device driver. + */ + +typedef struct ns_rcte { + u32 word_1; + u32 buffer_handle; + u32 dma_address; + u32 aal5_crc32; +} ns_rcte; + +#define NS_RCTE_BSFB 0x00200000 /* Rev. D only */ +#define NS_RCTE_NZGFC 0x00100000 +#define NS_RCTE_CONNECTOPEN 0x00080000 +#define NS_RCTE_AALMASK 0x00070000 +#define NS_RCTE_AAL0 0x00000000 +#define NS_RCTE_AAL34 0x00010000 +#define NS_RCTE_AAL5 0x00020000 +#define NS_RCTE_RCQ 0x00030000 +#define NS_RCTE_RAWCELLINTEN 0x00008000 +#define NS_RCTE_RXCONSTCELLADDR 0x00004000 +#define NS_RCTE_BUFFVALID 0x00002000 +#define NS_RCTE_FBDSIZE 0x00001000 +#define NS_RCTE_EFCI 0x00000800 +#define NS_RCTE_CLP 0x00000400 +#define NS_RCTE_CRCERROR 0x00000200 +#define NS_RCTE_CELLCOUNT_MASK 0x000001FF + +#define NS_RCTE_FBDSIZE_SM 0x00000000 +#define NS_RCTE_FBDSIZE_LG 0x00001000 + +#define NS_RCT_ENTRY_SIZE 4 /* Number of dwords */ + + /* NOTE: We could make macros to contruct the first word of the RCTE, + but that doesn't seem to make much sense... */ + +/* + * FBD - Free Buffer Descriptor + * + * Written by the device driver using via the command register. + */ + +typedef struct ns_fbd { + u32 buffer_handle; + u32 dma_address; +} ns_fbd; + +/* + * TST - Transmit Schedule Table + * + * Written by the device driver. + */ + +typedef u32 ns_tste; + +#define NS_TST_OPCODE_MASK 0x60000000 + +#define NS_TST_OPCODE_NULL 0x00000000 /* Insert null cell */ +#define NS_TST_OPCODE_FIXED 0x20000000 /* Cell from a fixed rate channel */ +#define NS_TST_OPCODE_VARIABLE 0x40000000 +#define NS_TST_OPCODE_END 0x60000000 /* Jump */ + +#define ns_tste_make(opcode, sramad) (opcode | sramad) + + /* NOTE: + + - When the opcode is FIXED, sramad specifies the SRAM address of the + SCD for that fixed rate channel. + - When the opcode is END, sramad specifies the SRAM address of the + location of the next TST entry to read. + */ + +/* + * SCD - Segmentation Channel Descriptor + * + * Written by both the device driver and the NICStAR + */ + +typedef struct ns_scd { + u32 word_1; + u32 word_2; + u32 partial_aal5_crc; + u32 reserved; + ns_scqe cache_a; + ns_scqe cache_b; +} ns_scd; + +#define NS_SCD_BASE_MASK_VAR 0xFFFFE000 /* Variable rate */ +#define NS_SCD_BASE_MASK_FIX 0xFFFFFC00 /* Fixed rate */ +#define NS_SCD_TAIL_MASK_VAR 0x00001FF0 +#define NS_SCD_TAIL_MASK_FIX 0x000003F0 +#define NS_SCD_HEAD_MASK_VAR 0x00001FF0 +#define NS_SCD_HEAD_MASK_FIX 0x000003F0 +#define NS_SCD_XMITFOREVER 0x02000000 + + /* NOTE: There are other fields in word 2 of the SCD, but as they should + not be needed in the device driver they are not defined here. */ + +/* NICStAR local SRAM memory map */ + +#define NS_RCT 0x00000 +#define NS_RCT_32_END 0x03FFF +#define NS_RCT_128_END 0x0FFFF +#define NS_UNUSED_32 0x04000 +#define NS_UNUSED_128 0x10000 +#define NS_UNUSED_END 0x1BFFF +#define NS_TST_FRSCD 0x1C000 +#define NS_TST_FRSCD_END 0x1E7DB +#define NS_VRSCD2 0x1E7DC +#define NS_VRSCD2_END 0x1E7E7 +#define NS_VRSCD1 0x1E7E8 +#define NS_VRSCD1_END 0x1E7F3 +#define NS_VRSCD0 0x1E7F4 +#define NS_VRSCD0_END 0x1E7FF +#define NS_RXFIFO 0x1E800 +#define NS_RXFIFO_END 0x1F7FF +#define NS_SMFBQ 0x1F800 +#define NS_SMFBQ_END 0x1FBFF +#define NS_LGFBQ 0x1FC00 +#define NS_LGFBQ_END 0x1FFFF + +/* NISCtAR operation registers */ + +/* See Section 3.4 of `IDT77211 NICStAR User Manual' from www.idt.com */ + +enum ns_regs { + DR0 = 0x00, /* Data Register 0 R/W */ + DR1 = 0x04, /* Data Register 1 W */ + DR2 = 0x08, /* Data Register 2 W */ + DR3 = 0x0C, /* Data Register 3 W */ + CMD = 0x10, /* Command W */ + CFG = 0x14, /* Configuration R/W */ + STAT = 0x18, /* Status R/W */ + RSQB = 0x1C, /* Receive Status Queue Base W */ + RSQT = 0x20, /* Receive Status Queue Tail R */ + RSQH = 0x24, /* Receive Status Queue Head W */ + CDC = 0x28, /* Cell Drop Counter R/clear */ + VPEC = 0x2C, /* VPI/VCI Lookup Error Count R/clear */ + ICC = 0x30, /* Invalid Cell Count R/clear */ + RAWCT = 0x34, /* Raw Cell Tail R */ + TMR = 0x38, /* Timer R */ + TSTB = 0x3C, /* Transmit Schedule Table Base R/W */ + TSQB = 0x40, /* Transmit Status Queue Base W */ + TSQT = 0x44, /* Transmit Status Queue Tail R */ + TSQH = 0x48, /* Transmit Status Queue Head W */ + GP = 0x4C, /* General Purpose R/W */ + VPM = 0x50 /* VPI/VCI Mask W */ +}; + +/* NICStAR commands issued to the CMD register */ + +/* Top 4 bits are command opcode, lower 28 are parameters. */ + +#define NS_CMD_NO_OPERATION 0x00000000 + /* params always 0 */ + +#define NS_CMD_OPENCLOSE_CONNECTION 0x20000000 + /* b19{1=open,0=close} b18-2{SRAM addr} */ + +#define NS_CMD_WRITE_SRAM 0x40000000 + /* b18-2{SRAM addr} b1-0{burst size} */ + +#define NS_CMD_READ_SRAM 0x50000000 + /* b18-2{SRAM addr} */ + +#define NS_CMD_WRITE_FREEBUFQ 0x60000000 + /* b0{large buf indicator} */ + +#define NS_CMD_READ_UTILITY 0x80000000 + /* b8{1=select UTL_CS1} b9{1=select UTL_CS0} b7-0{bus addr} */ + +#define NS_CMD_WRITE_UTILITY 0x90000000 + /* b8{1=select UTL_CS1} b9{1=select UTL_CS0} b7-0{bus addr} */ + +#define NS_CMD_OPEN_CONNECTION (NS_CMD_OPENCLOSE_CONNECTION | 0x00080000) +#define NS_CMD_CLOSE_CONNECTION NS_CMD_OPENCLOSE_CONNECTION + +/* NICStAR configuration bits */ + +#define NS_CFG_SWRST 0x80000000 /* Software Reset */ +#define NS_CFG_RXPATH 0x20000000 /* Receive Path Enable */ +#define NS_CFG_SMBUFSIZE_MASK 0x18000000 /* Small Receive Buffer Size */ +#define NS_CFG_LGBUFSIZE_MASK 0x06000000 /* Large Receive Buffer Size */ +#define NS_CFG_EFBIE 0x01000000 /* Empty Free Buffer Queue + Interrupt Enable */ +#define NS_CFG_RSQSIZE_MASK 0x00C00000 /* Receive Status Queue Size */ +#define NS_CFG_ICACCEPT 0x00200000 /* Invalid Cell Accept */ +#define NS_CFG_IGNOREGFC 0x00100000 /* Ignore General Flow Control */ +#define NS_CFG_VPIBITS_MASK 0x000C0000 /* VPI/VCI Bits Size Select */ +#define NS_CFG_RCTSIZE_MASK 0x00030000 /* Receive Connection Table Size */ +#define NS_CFG_VCERRACCEPT 0x00008000 /* VPI/VCI Error Cell Accept */ +#define NS_CFG_RXINT_MASK 0x00007000 /* End of Receive PDU Interrupt + Handling */ +#define NS_CFG_RAWIE 0x00000800 /* Raw Cell Qu' Interrupt Enable */ +#define NS_CFG_RSQAFIE 0x00000400 /* Receive Queue Almost Full + Interrupt Enable */ +#define NS_CFG_RXRM 0x00000200 /* Receive RM Cells */ +#define NS_CFG_TMRROIE 0x00000080 /* Timer Roll Over Interrupt + Enable */ +#define NS_CFG_TXEN 0x00000020 /* Transmit Operation Enable */ +#define NS_CFG_TXIE 0x00000010 /* Transmit Status Interrupt + Enable */ +#define NS_CFG_TXURIE 0x00000008 /* Transmit Under-run Interrupt + Enable */ +#define NS_CFG_UMODE 0x00000004 /* Utopia Mode (cell/byte) Select */ +#define NS_CFG_TSQFIE 0x00000002 /* Transmit Status Queue Full + Interrupt Enable */ +#define NS_CFG_PHYIE 0x00000001 /* PHY Interrupt Enable */ + +#define NS_CFG_SMBUFSIZE_48 0x00000000 +#define NS_CFG_SMBUFSIZE_96 0x08000000 +#define NS_CFG_SMBUFSIZE_240 0x10000000 +#define NS_CFG_SMBUFSIZE_2048 0x18000000 + +#define NS_CFG_LGBUFSIZE_2048 0x00000000 +#define NS_CFG_LGBUFSIZE_4096 0x02000000 +#define NS_CFG_LGBUFSIZE_8192 0x04000000 +#define NS_CFG_LGBUFSIZE_16384 0x06000000 + +#define NS_CFG_RSQSIZE_2048 0x00000000 +#define NS_CFG_RSQSIZE_4096 0x00400000 +#define NS_CFG_RSQSIZE_8192 0x00800000 + +#define NS_CFG_VPIBITS_0 0x00000000 +#define NS_CFG_VPIBITS_1 0x00040000 +#define NS_CFG_VPIBITS_2 0x00080000 +#define NS_CFG_VPIBITS_8 0x000C0000 + +#define NS_CFG_RCTSIZE_4096_ENTRIES 0x00000000 +#define NS_CFG_RCTSIZE_8192_ENTRIES 0x00010000 +#define NS_CFG_RCTSIZE_16384_ENTRIES 0x00020000 + +#define NS_CFG_RXINT_NOINT 0x00000000 +#define NS_CFG_RXINT_NODELAY 0x00001000 +#define NS_CFG_RXINT_314US 0x00002000 +#define NS_CFG_RXINT_624US 0x00003000 +#define NS_CFG_RXINT_899US 0x00004000 + +/* NICStAR STATus bits */ + +#define NS_STAT_SFBQC_MASK 0xFF000000 /* hi 8 bits Small Buffer Queue Count */ +#define NS_STAT_LFBQC_MASK 0x00FF0000 /* hi 8 bits Large Buffer Queue Count */ +#define NS_STAT_TSIF 0x00008000 /* Transmit Status Queue Indicator */ +#define NS_STAT_TXICP 0x00004000 /* Transmit Incomplete PDU */ +#define NS_STAT_TSQF 0x00001000 /* Transmit Status Queue Full */ +#define NS_STAT_TMROF 0x00000800 /* Timer Overflow */ +#define NS_STAT_PHYI 0x00000400 /* PHY Device Interrupt */ +#define NS_STAT_CMDBZ 0x00000200 /* Command Busy */ +#define NS_STAT_SFBQF 0x00000100 /* Small Buffer Queue Full */ +#define NS_STAT_LFBQF 0x00000080 /* Large Buffer Queue Full */ +#define NS_STAT_RSQF 0x00000040 /* Receive Status Queue Full */ +#define NS_STAT_EOPDU 0x00000020 /* End of PDU */ +#define NS_STAT_RAWCF 0x00000010 /* Raw Cell Flag */ +#define NS_STAT_SFBQE 0x00000008 /* Small Buffer Queue Empty */ +#define NS_STAT_LFBQE 0x00000004 /* Large Buffer Queue Empty */ +#define NS_STAT_RSQAF 0x00000002 /* Receive Status Queue Almost Full */ + +#define ns_stat_sfbqc_get(stat) (((stat) & NS_STAT_SFBQC_MASK) >> 23) +#define ns_stat_lfbqc_get(stat) (((stat) & NS_STAT_LFBQC_MASK) >> 15) + +/* #defines which depend on other #defines */ + +#define NS_TST0 NS_TST_FRSCD +#define NS_TST1 (NS_TST_FRSCD + NS_TST_NUM_ENTRIES + 1) + +#define NS_FRSCD (NS_TST1 + NS_TST_NUM_ENTRIES + 1) +#define NS_FRSCD_SIZE 12 /* 12 dwords */ +#define NS_FRSCD_NUM ((NS_TST_FRSCD_END + 1 - NS_FRSCD) / NS_FRSCD_SIZE) + +#if (NS_SMBUFSIZE == 48) +#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_48 +#elif (NS_SMBUFSIZE == 96) +#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_96 +#elif (NS_SMBUFSIZE == 240) +#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_240 +#elif (NS_SMBUFSIZE == 2048) +#define NS_CFG_SMBUFSIZE NS_CFG_SMBUFSIZE_2048 +#else +#error NS_SMBUFSIZE is incorrect in nicstar.h +#endif /* NS_SMBUFSIZE */ + +#if (NS_LGBUFSIZE == 2048) +#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_2048 +#elif (NS_LGBUFSIZE == 4096) +#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_4096 +#elif (NS_LGBUFSIZE == 8192) +#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_8192 +#elif (NS_LGBUFSIZE == 16384) +#define NS_CFG_LGBUFSIZE NS_CFG_LGBUFSIZE_16384 +#else +#error NS_LGBUFSIZE is incorrect in nicstar.h +#endif /* NS_LGBUFSIZE */ + +#if (NS_RSQSIZE == 2048) +#define NS_CFG_RSQSIZE NS_CFG_RSQSIZE_2048 +#elif (NS_RSQSIZE == 4096) +#define NS_CFG_RSQSIZE NS_CFG_RSQSIZE_4096 +#elif (NS_RSQSIZE == 8192) +#define NS_CFG_RSQSIZE NS_CFG_RSQSIZE_8192 +#else +#error NS_RSQSIZE is incorrect in nicstar.h +#endif /* NS_RSQSIZE */ + +#if (NS_VPIBITS == 0) +#define NS_CFG_VPIBITS NS_CFG_VPIBITS_0 +#elif (NS_VPIBITS == 1) +#define NS_CFG_VPIBITS NS_CFG_VPIBITS_1 +#elif (NS_VPIBITS == 2) +#define NS_CFG_VPIBITS NS_CFG_VPIBITS_2 +#elif (NS_VPIBITS == 8) +#define NS_CFG_VPIBITS NS_CFG_VPIBITS_8 +#else +#error NS_VPIBITS is incorrect in nicstar.h +#endif /* NS_VPIBITS */ + +#ifdef RCQ_SUPPORT +#define NS_CFG_RAWIE_OPT NS_CFG_RAWIE +#else +#define NS_CFG_RAWIE_OPT 0x00000000 +#endif /* RCQ_SUPPORT */ + +#ifdef ENABLE_TSQFIE +#define NS_CFG_TSQFIE_OPT NS_CFG_TSQFIE +#else +#define NS_CFG_TSQFIE_OPT 0x00000000 +#endif /* ENABLE_TSQFIE */ + +/* PCI stuff */ + +#ifndef PCI_VENDOR_ID_IDT +#define PCI_VENDOR_ID_IDT 0x111D +#endif /* PCI_VENDOR_ID_IDT */ + +#ifndef PCI_DEVICE_ID_IDT_IDT77201 +#define PCI_DEVICE_ID_IDT_IDT77201 0x0001 +#endif /* PCI_DEVICE_ID_IDT_IDT77201 */ + +/* Device driver structures */ + +struct ns_skb_prv { + u32 buf_type; /* BUF_SM/BUF_LG/BUF_NONE */ + u32 dma; + int iovcnt; +}; + +#define NS_PRV_BUFTYPE(skb) \ + (((struct ns_skb_prv *)(ATM_SKB(skb)+1))->buf_type) +#define NS_PRV_DMA(skb) \ + (((struct ns_skb_prv *)(ATM_SKB(skb)+1))->dma) +#define NS_PRV_IOVCNT(skb) \ + (((struct ns_skb_prv *)(ATM_SKB(skb)+1))->iovcnt) + +typedef struct tsq_info { + void *org; + dma_addr_t dma; + ns_tsi *base; + ns_tsi *next; + ns_tsi *last; +} tsq_info; + +typedef struct scq_info { + void *org; + dma_addr_t dma; + ns_scqe *base; + ns_scqe *last; + ns_scqe *next; + volatile ns_scqe *tail; /* Not related to the nicstar register */ + unsigned num_entries; + struct sk_buff **skb; /* Pointer to an array of pointers + to the sk_buffs used for tx */ + u32 scd; /* SRAM address of the corresponding + SCD */ + int tbd_count; /* Only meaningful on variable rate */ + wait_queue_head_t scqfull_waitq; + volatile char full; /* SCQ full indicator */ + spinlock_t lock; /* SCQ spinlock */ +} scq_info; + +typedef struct rsq_info { + void *org; + dma_addr_t dma; + ns_rsqe *base; + ns_rsqe *next; + ns_rsqe *last; +} rsq_info; + +typedef struct skb_pool { + volatile int count; /* number of buffers in the queue */ + struct sk_buff_head queue; +} skb_pool; + +/* NOTE: for small and large buffer pools, the count is not used, as the + actual value used for buffer management is the one read from the + card. */ + +typedef struct vc_map { + volatile unsigned int tx:1; /* TX vc? */ + volatile unsigned int rx:1; /* RX vc? */ + struct atm_vcc *tx_vcc, *rx_vcc; + struct sk_buff *rx_iov; /* RX iovector skb */ + scq_info *scq; /* To keep track of the SCQ */ + u32 cbr_scd; /* SRAM address of the corresponding + SCD. 0x00000000 for UBR/VBR/ABR */ + int tbd_count; +} vc_map; + +typedef struct ns_dev { + int index; /* Card ID to the device driver */ + int sram_size; /* In k x 32bit words. 32 or 128 */ + void __iomem *membase; /* Card's memory base address */ + unsigned long max_pcr; + int rct_size; /* Number of entries */ + int vpibits; + int vcibits; + struct pci_dev *pcidev; + struct idr idr; + struct atm_dev *atmdev; + tsq_info tsq; + rsq_info rsq; + scq_info *scq0, *scq1, *scq2; /* VBR SCQs */ + skb_pool sbpool; /* Small buffers */ + skb_pool lbpool; /* Large buffers */ + skb_pool hbpool; /* Pre-allocated huge buffers */ + skb_pool iovpool; /* iovector buffers */ + volatile int efbie; /* Empty free buf. queue int. enabled */ + volatile u32 tst_addr; /* SRAM address of the TST in use */ + volatile int tst_free_entries; + vc_map vcmap[NS_MAX_RCTSIZE]; + vc_map *tste2vc[NS_TST_NUM_ENTRIES]; + vc_map *scd2vc[NS_FRSCD_NUM]; + buf_nr sbnr; + buf_nr lbnr; + buf_nr hbnr; + buf_nr iovnr; + int sbfqc; + int lbfqc; + struct sk_buff *sm_handle; + u32 sm_addr; + struct sk_buff *lg_handle; + u32 lg_addr; + struct sk_buff *rcbuf; /* Current raw cell buffer */ + struct ns_rcqe *rawcell; + u32 rawch; /* Raw cell queue head */ + unsigned intcnt; /* Interrupt counter */ + spinlock_t int_lock; /* Interrupt lock */ + spinlock_t res_lock; /* Card resource lock */ +} ns_dev; + + /* NOTE: Each tste2vc entry relates a given TST entry to the corresponding + CBR vc. If the entry is not allocated, it must be NULL. + + There are two TSTs so the driver can modify them on the fly + without stopping the transmission. + + scd2vc allows us to find out unused fixed rate SCDs, because + they must have a NULL pointer here. */ + +#endif /* _LINUX_NICSTAR_H_ */ diff --git a/drivers/atm/nicstarmac.c b/drivers/atm/nicstarmac.c new file mode 100644 index 000000000..e0dda9062 --- /dev/null +++ b/drivers/atm/nicstarmac.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * this file included by nicstar.c + */ + +/* + * nicstarmac.c + * Read this ForeRunner's MAC address from eprom/eeprom + */ + +#include + +typedef void __iomem *virt_addr_t; + +#define CYCLE_DELAY 5 + +/* + This was the original definition +#define osp_MicroDelay(microsec) \ + do { int _i = 4*microsec; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0) +*/ +#define osp_MicroDelay(microsec) {unsigned long useconds = (microsec); \ + udelay((useconds));} +/* + * The following tables represent the timing diagrams found in + * the Data Sheet for the Xicor X25020 EEProm. The #defines below + * represent the bits in the NICStAR's General Purpose register + * that must be toggled for the corresponding actions on the EEProm + * to occur. + */ + +/* Write Data To EEProm from SI line on rising edge of CLK */ +/* Read Data From EEProm on falling edge of CLK */ + +#define CS_HIGH 0x0002 /* Chip select high */ +#define CS_LOW 0x0000 /* Chip select low (active low) */ +#define CLK_HIGH 0x0004 /* Clock high */ +#define CLK_LOW 0x0000 /* Clock low */ +#define SI_HIGH 0x0001 /* Serial input data high */ +#define SI_LOW 0x0000 /* Serial input data low */ + +/* Read Status Register = 0000 0101b */ +#if 0 +static u_int32_t rdsrtab[] = { + CS_HIGH | CLK_HIGH, + CS_LOW | CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW | SI_HIGH, + CLK_HIGH | SI_HIGH, /* 1 */ + CLK_LOW | SI_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW | SI_HIGH, + CLK_HIGH | SI_HIGH /* 1 */ +}; +#endif /* 0 */ + +/* Read from EEPROM = 0000 0011b */ +static u_int32_t readtab[] = { + /* + CS_HIGH | CLK_HIGH, + */ + CS_LOW | CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW, + CLK_HIGH, /* 0 */ + CLK_LOW | SI_HIGH, + CLK_HIGH | SI_HIGH, /* 1 */ + CLK_LOW | SI_HIGH, + CLK_HIGH | SI_HIGH /* 1 */ +}; + +/* Clock to read from/write to the eeprom */ +static u_int32_t clocktab[] = { + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW, + CLK_HIGH, + CLK_LOW +}; + +#define NICSTAR_REG_WRITE(bs, reg, val) \ + while ( readl(bs + STAT) & 0x0200 ) ; \ + writel((val),(base)+(reg)) +#define NICSTAR_REG_READ(bs, reg) \ + readl((base)+(reg)) +#define NICSTAR_REG_GENERAL_PURPOSE GP + +/* + * This routine will clock the Read_Status_reg function into the X2520 + * eeprom, then pull the result from bit 16 of the NicSTaR's General Purpose + * register. + */ +#if 0 +u_int32_t nicstar_read_eprom_status(virt_addr_t base) +{ + u_int32_t val; + u_int32_t rbyte; + int32_t i, j; + + /* Send read instruction */ + val = NICSTAR_REG_READ(base, NICSTAR_REG_GENERAL_PURPOSE) & 0xFFFFFFF0; + + for (i = 0; i < ARRAY_SIZE(rdsrtab); i++) { + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | rdsrtab[i])); + osp_MicroDelay(CYCLE_DELAY); + } + + /* Done sending instruction - now pull data off of bit 16, MSB first */ + /* Data clocked out of eeprom on falling edge of clock */ + + rbyte = 0; + for (i = 7, j = 0; i >= 0; i--) { + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | clocktab[j++])); + rbyte |= (((NICSTAR_REG_READ(base, NICSTAR_REG_GENERAL_PURPOSE) + & 0x00010000) >> 16) << i); + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | clocktab[j++])); + osp_MicroDelay(CYCLE_DELAY); + } + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, 2); + osp_MicroDelay(CYCLE_DELAY); + return rbyte; +} +#endif /* 0 */ + +/* + * This routine will clock the Read_data function into the X2520 + * eeprom, followed by the address to read from, through the NicSTaR's General + * Purpose register. + */ + +static u_int8_t read_eprom_byte(virt_addr_t base, u_int8_t offset) +{ + u_int32_t val = 0; + int i, j = 0; + u_int8_t tempread = 0; + + val = NICSTAR_REG_READ(base, NICSTAR_REG_GENERAL_PURPOSE) & 0xFFFFFFF0; + + /* Send READ instruction */ + for (i = 0; i < ARRAY_SIZE(readtab); i++) { + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | readtab[i])); + osp_MicroDelay(CYCLE_DELAY); + } + + /* Next, we need to send the byte address to read from */ + for (i = 7; i >= 0; i--) { + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | clocktab[j++] | ((offset >> i) & 1))); + osp_MicroDelay(CYCLE_DELAY); + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | clocktab[j++] | ((offset >> i) & 1))); + osp_MicroDelay(CYCLE_DELAY); + } + + j = 0; + + /* Now, we can read data from the eeprom by clocking it in */ + for (i = 7; i >= 0; i--) { + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | clocktab[j++])); + osp_MicroDelay(CYCLE_DELAY); + tempread |= + (((NICSTAR_REG_READ(base, NICSTAR_REG_GENERAL_PURPOSE) + & 0x00010000) >> 16) << i); + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | clocktab[j++])); + osp_MicroDelay(CYCLE_DELAY); + } + + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, 2); + osp_MicroDelay(CYCLE_DELAY); + return tempread; +} + +static void nicstar_init_eprom(virt_addr_t base) +{ + u_int32_t val; + + /* + * turn chip select off + */ + val = NICSTAR_REG_READ(base, NICSTAR_REG_GENERAL_PURPOSE) & 0xFFFFFFF0; + + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | CS_HIGH | CLK_HIGH)); + osp_MicroDelay(CYCLE_DELAY); + + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | CS_HIGH | CLK_LOW)); + osp_MicroDelay(CYCLE_DELAY); + + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | CS_HIGH | CLK_HIGH)); + osp_MicroDelay(CYCLE_DELAY); + + NICSTAR_REG_WRITE(base, NICSTAR_REG_GENERAL_PURPOSE, + (val | CS_HIGH | CLK_LOW)); + osp_MicroDelay(CYCLE_DELAY); +} + +/* + * This routine will be the interface to the ReadPromByte function + * above. + */ + +static void +nicstar_read_eprom(virt_addr_t base, + u_int8_t prom_offset, u_int8_t * buffer, u_int32_t nbytes) +{ + u_int i; + + for (i = 0; i < nbytes; i++) { + buffer[i] = read_eprom_byte(base, prom_offset); + ++prom_offset; + osp_MicroDelay(CYCLE_DELAY); + } +} diff --git a/drivers/atm/nicstarmac.copyright b/drivers/atm/nicstarmac.copyright new file mode 100644 index 000000000..180531a83 --- /dev/null +++ b/drivers/atm/nicstarmac.copyright @@ -0,0 +1,61 @@ +/* nicstar.c v0.22 Jawaid Bazyar (bazyar@hypermall.com) + * nicstar.c, M. Welsh (matt.welsh@cl.cam.ac.uk) + * + * Hacked October, 1997 by Jawaid Bazyar, Interlink Advertising Services Inc. + * http://www.hypermall.com/ + * 10/1/97 - commented out CFG_PHYIE bit - we don't care when the PHY + * interrupts us (except possibly for removal/insertion of the cable?) + * 10/4/97 - began heavy inline documentation of the code. Corrected typos + * and spelling mistakes. + * 10/5/97 - added code to handle PHY interrupts, disable PHY on + * loss of link, and correctly re-enable PHY when link is + * re-established. (put back CFG_PHYIE) + * + * Modified to work with the IDT7721 nicstar -- AAL5 (tested) only. + * + * R. D. Rechenmacher , Aug. 6, 1997 + * + * Linux driver for the IDT77201 NICStAR PCI ATM controller. + * PHY component is expected to be 155 Mbps S/UNI-Lite or IDT 77155; + * see init_nicstar() for PHY initialization to change this. This driver + * expects the Linux ATM stack to support scatter-gather lists + * (skb->atm.iovcnt != 0) for Rx skb's passed to vcc->push. + * + * Implementing minimal-copy of received data: + * IDT always receives data into a small buffer, then large buffers + * as needed. This means that data must always be copied to create + * the linear buffer needed by most non-ATM protocol stacks (e.g. IP) + * Fix is simple: make large buffers large enough to hold entire + * SDU, and leave bytes empty at the start. Then + * copy small buffer contents to head of large buffer. + * Trick is to avoid fragmenting Linux, due to need for a lot of large + * buffers. This is done by 2 things: + * 1) skb->destructor / skb->atm.recycle_buffer + * combined, allow nicstar_free_rx_skb to be called to + * recycle large data buffers + * 2) skb_clone of received buffers + * See nicstar_free_rx_skb and linearize_buffer for implementation + * details. + * + * + * + * Copyright (c) 1996 University of Cambridge Computer Laboratory + * + * 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. + * + * M. Welsh, 6 July 1996 + * + * + */ diff --git a/drivers/atm/solos-attrlist.c b/drivers/atm/solos-attrlist.c new file mode 100644 index 000000000..1830d1b86 --- /dev/null +++ b/drivers/atm/solos-attrlist.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +SOLOS_ATTR_RO(DriverVersion) +SOLOS_ATTR_RO(APIVersion) +SOLOS_ATTR_RO(FirmwareVersion) +SOLOS_ATTR_RO(Version) +// SOLOS_ATTR_RO(DspVersion) +// SOLOS_ATTR_RO(CommonHandshake) +SOLOS_ATTR_RO(Connected) +SOLOS_ATTR_RO(OperationalMode) +SOLOS_ATTR_RO(State) +SOLOS_ATTR_RO(Watchdog) +SOLOS_ATTR_RO(OperationProgress) +SOLOS_ATTR_RO(LastFailed) +SOLOS_ATTR_RO(TxBitRate) +SOLOS_ATTR_RO(RxBitRate) +// SOLOS_ATTR_RO(DeltACTATPds) +// SOLOS_ATTR_RO(DeltACTATPus) +SOLOS_ATTR_RO(TxATTNDR) +SOLOS_ATTR_RO(RxATTNDR) +SOLOS_ATTR_RO(AnnexType) +SOLOS_ATTR_RO(GeneralFailure) +SOLOS_ATTR_RO(InterleaveDpDn) +SOLOS_ATTR_RO(InterleaveDpUp) +SOLOS_ATTR_RO(RSCorrectedErrorsDn) +SOLOS_ATTR_RO(RSUnCorrectedErrorsDn) +SOLOS_ATTR_RO(RSCorrectedErrorsUp) +SOLOS_ATTR_RO(RSUnCorrectedErrorsUp) +SOLOS_ATTR_RO(InterleaveRDn) +SOLOS_ATTR_RO(InterleaveRUp) +SOLOS_ATTR_RO(BisRDn) +SOLOS_ATTR_RO(BisRUp) +SOLOS_ATTR_RO(INPdown) +SOLOS_ATTR_RO(INPup) +SOLOS_ATTR_RO(ShowtimeStart) +SOLOS_ATTR_RO(ATURVendor) +SOLOS_ATTR_RO(ATUCCountry) +SOLOS_ATTR_RO(ATURANSIRev) +SOLOS_ATTR_RO(ATURANSISTD) +SOLOS_ATTR_RO(ATUCANSIRev) +SOLOS_ATTR_RO(ATUCANSIId) +SOLOS_ATTR_RO(ATUCANSISTD) +SOLOS_ATTR_RO(DataBoost) +SOLOS_ATTR_RO(LocalITUCountryCode) +SOLOS_ATTR_RO(LocalSEF) +SOLOS_ATTR_RO(LocalEndLOS) +SOLOS_ATTR_RO(LocalSNRMargin) +SOLOS_ATTR_RO(LocalLineAttn) +SOLOS_ATTR_RO(RawAttn) +SOLOS_ATTR_RO(LocalTxPower) +SOLOS_ATTR_RO(RemoteTxPower) +SOLOS_ATTR_RO(RemoteSEF) +SOLOS_ATTR_RO(RemoteLOS) +SOLOS_ATTR_RO(RemoteLineAttn) +SOLOS_ATTR_RO(RemoteSNRMargin) +SOLOS_ATTR_RO(LineUpCount) +SOLOS_ATTR_RO(SRACnt) +SOLOS_ATTR_RO(SRACntUp) +SOLOS_ATTR_RO(ProfileStatus) +SOLOS_ATTR_RW(Action) +SOLOS_ATTR_RW(ActivateLine) +SOLOS_ATTR_RO(LineStatus) +SOLOS_ATTR_RW(HostControl) +SOLOS_ATTR_RW(AutoStart) +SOLOS_ATTR_RW(Failsafe) +SOLOS_ATTR_RW(ShowtimeLed) +SOLOS_ATTR_RW(Retrain) +SOLOS_ATTR_RW(Defaults) +SOLOS_ATTR_RW(LineMode) +SOLOS_ATTR_RW(Profile) +SOLOS_ATTR_RW(DetectNoise) +SOLOS_ATTR_RW(BisAForceSNRMarginDn) +SOLOS_ATTR_RW(BisMForceSNRMarginDn) +SOLOS_ATTR_RW(BisAMaxMargin) +SOLOS_ATTR_RW(BisMMaxMargin) +SOLOS_ATTR_RW(AnnexAForceSNRMarginDn) +SOLOS_ATTR_RW(AnnexAMaxMargin) +SOLOS_ATTR_RW(AnnexMMaxMargin) +SOLOS_ATTR_RO(SupportedAnnexes) +SOLOS_ATTR_RO(Status) +SOLOS_ATTR_RO(TotalStart) +SOLOS_ATTR_RO(RecentShowtimeStart) +SOLOS_ATTR_RO(TotalRxBlocks) +SOLOS_ATTR_RO(TotalTxBlocks) diff --git a/drivers/atm/solos-pci.c b/drivers/atm/solos-pci.c new file mode 100644 index 000000000..d3c30a28c --- /dev/null +++ b/drivers/atm/solos-pci.c @@ -0,0 +1,1496 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Solos PCI ADSL2+ card, designed to support Linux by + * Traverse Technologies -- https://www.traverse.com.au/ + * Xrio Limited -- http://www.xrio.com/ + * + * Copyright © 2008 Traverse Technologies + * Copyright © 2008 Intel Corporation + * + * Authors: Nathan Williams + * David Woodhouse + * Treker Chen + */ + +#define DEBUG +#define VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VERSION "1.04" +#define DRIVER_VERSION 0x01 +#define PTAG "solos-pci" + +#define CONFIG_RAM_SIZE 128 +#define FLAGS_ADDR 0x7C +#define IRQ_EN_ADDR 0x78 +#define FPGA_VER 0x74 +#define IRQ_CLEAR 0x70 +#define WRITE_FLASH 0x6C +#define PORTS 0x68 +#define FLASH_BLOCK 0x64 +#define FLASH_BUSY 0x60 +#define FPGA_MODE 0x5C +#define FLASH_MODE 0x58 +#define GPIO_STATUS 0x54 +#define DRIVER_VER 0x50 +#define TX_DMA_ADDR(port) (0x40 + (4 * (port))) +#define RX_DMA_ADDR(port) (0x30 + (4 * (port))) + +#define DATA_RAM_SIZE 32768 +#define BUF_SIZE 2048 +#define OLD_BUF_SIZE 4096 /* For FPGA versions <= 2*/ +/* Old boards use ATMEL AD45DB161D flash */ +#define ATMEL_FPGA_PAGE 528 /* FPGA flash page size*/ +#define ATMEL_SOLOS_PAGE 512 /* Solos flash page size*/ +#define ATMEL_FPGA_BLOCK (ATMEL_FPGA_PAGE * 8) /* FPGA block size*/ +#define ATMEL_SOLOS_BLOCK (ATMEL_SOLOS_PAGE * 8) /* Solos block size*/ +/* Current boards use M25P/M25PE SPI flash */ +#define SPI_FLASH_BLOCK (256 * 64) + +#define RX_BUF(card, nr) ((card->buffers) + (nr)*(card->buffer_size)*2) +#define TX_BUF(card, nr) ((card->buffers) + (nr)*(card->buffer_size)*2 + (card->buffer_size)) +#define FLASH_BUF ((card->buffers) + 4*(card->buffer_size)*2) + +#define RX_DMA_SIZE 2048 + +#define FPGA_VERSION(a,b) (((a) << 8) + (b)) +#define LEGACY_BUFFERS 2 +#define DMA_SUPPORTED 4 + +static int reset = 0; +static int atmdebug = 0; +static int firmware_upgrade = 0; +static int fpga_upgrade = 0; +static int db_firmware_upgrade = 0; +static int db_fpga_upgrade = 0; + +struct pkt_hdr { + __le16 size; + __le16 vpi; + __le16 vci; + __le16 type; +}; + +struct solos_skb_cb { + struct atm_vcc *vcc; + uint32_t dma_addr; +}; + + +#define SKB_CB(skb) ((struct solos_skb_cb *)skb->cb) + +#define PKT_DATA 0 +#define PKT_COMMAND 1 +#define PKT_POPEN 3 +#define PKT_PCLOSE 4 +#define PKT_STATUS 5 + +struct solos_card { + void __iomem *config_regs; + void __iomem *buffers; + int nr_ports; + int tx_mask; + struct pci_dev *dev; + struct atm_dev *atmdev[4]; + struct tasklet_struct tlet; + spinlock_t tx_lock; + spinlock_t tx_queue_lock; + spinlock_t cli_queue_lock; + spinlock_t param_queue_lock; + struct list_head param_queue; + struct sk_buff_head tx_queue[4]; + struct sk_buff_head cli_queue[4]; + struct sk_buff *tx_skb[4]; + struct sk_buff *rx_skb[4]; + unsigned char *dma_bounce; + wait_queue_head_t param_wq; + wait_queue_head_t fw_wq; + int using_dma; + int dma_alignment; + int fpga_version; + int buffer_size; + int atmel_flash; +}; + + +struct solos_param { + struct list_head list; + pid_t pid; + int port; + struct sk_buff *response; +}; + +#define SOLOS_CHAN(atmdev) ((int)(unsigned long)(atmdev)->phy_data) + +MODULE_AUTHOR("Traverse Technologies "); +MODULE_DESCRIPTION("Solos PCI driver"); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("solos-FPGA.bin"); +MODULE_FIRMWARE("solos-Firmware.bin"); +MODULE_FIRMWARE("solos-db-FPGA.bin"); +MODULE_PARM_DESC(reset, "Reset Solos chips on startup"); +MODULE_PARM_DESC(atmdebug, "Print ATM data"); +MODULE_PARM_DESC(firmware_upgrade, "Initiate Solos firmware upgrade"); +MODULE_PARM_DESC(fpga_upgrade, "Initiate FPGA upgrade"); +MODULE_PARM_DESC(db_firmware_upgrade, "Initiate daughter board Solos firmware upgrade"); +MODULE_PARM_DESC(db_fpga_upgrade, "Initiate daughter board FPGA upgrade"); +module_param(reset, int, 0444); +module_param(atmdebug, int, 0644); +module_param(firmware_upgrade, int, 0444); +module_param(fpga_upgrade, int, 0444); +module_param(db_firmware_upgrade, int, 0444); +module_param(db_fpga_upgrade, int, 0444); + +static void fpga_queue(struct solos_card *card, int port, struct sk_buff *skb, + struct atm_vcc *vcc); +static uint32_t fpga_tx(struct solos_card *); +static irqreturn_t solos_irq(int irq, void *dev_id); +static struct atm_vcc* find_vcc(struct atm_dev *dev, short vpi, int vci); +static int atm_init(struct solos_card *, struct device *); +static void atm_remove(struct solos_card *); +static int send_command(struct solos_card *card, int dev, const char *buf, size_t size); +static void solos_bh(unsigned long); +static int print_buffer(struct sk_buff *buf); + +static inline void solos_pop(struct atm_vcc *vcc, struct sk_buff *skb) +{ + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_any(skb); +} + +static ssize_t solos_param_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); + struct solos_card *card = atmdev->dev_data; + struct solos_param prm; + struct sk_buff *skb; + struct pkt_hdr *header; + int buflen; + + buflen = strlen(attr->attr.name) + 10; + + skb = alloc_skb(sizeof(*header) + buflen, GFP_KERNEL); + if (!skb) { + dev_warn(&card->dev->dev, "Failed to allocate sk_buff in solos_param_show()\n"); + return -ENOMEM; + } + + header = skb_put(skb, sizeof(*header)); + + buflen = snprintf((void *)&header[1], buflen - 1, + "L%05d\n%s\n", current->pid, attr->attr.name); + skb_put(skb, buflen); + + header->size = cpu_to_le16(buflen); + header->vpi = cpu_to_le16(0); + header->vci = cpu_to_le16(0); + header->type = cpu_to_le16(PKT_COMMAND); + + prm.pid = current->pid; + prm.response = NULL; + prm.port = SOLOS_CHAN(atmdev); + + spin_lock_irq(&card->param_queue_lock); + list_add(&prm.list, &card->param_queue); + spin_unlock_irq(&card->param_queue_lock); + + fpga_queue(card, prm.port, skb, NULL); + + wait_event_timeout(card->param_wq, prm.response, 5 * HZ); + + spin_lock_irq(&card->param_queue_lock); + list_del(&prm.list); + spin_unlock_irq(&card->param_queue_lock); + + if (!prm.response) + return -EIO; + + buflen = prm.response->len; + memcpy(buf, prm.response->data, buflen); + kfree_skb(prm.response); + + return buflen; +} + +static ssize_t solos_param_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); + struct solos_card *card = atmdev->dev_data; + struct solos_param prm; + struct sk_buff *skb; + struct pkt_hdr *header; + int buflen; + ssize_t ret; + + buflen = strlen(attr->attr.name) + 11 + count; + + skb = alloc_skb(sizeof(*header) + buflen, GFP_KERNEL); + if (!skb) { + dev_warn(&card->dev->dev, "Failed to allocate sk_buff in solos_param_store()\n"); + return -ENOMEM; + } + + header = skb_put(skb, sizeof(*header)); + + buflen = snprintf((void *)&header[1], buflen - 1, + "L%05d\n%s\n%s\n", current->pid, attr->attr.name, buf); + + skb_put(skb, buflen); + header->size = cpu_to_le16(buflen); + header->vpi = cpu_to_le16(0); + header->vci = cpu_to_le16(0); + header->type = cpu_to_le16(PKT_COMMAND); + + prm.pid = current->pid; + prm.response = NULL; + prm.port = SOLOS_CHAN(atmdev); + + spin_lock_irq(&card->param_queue_lock); + list_add(&prm.list, &card->param_queue); + spin_unlock_irq(&card->param_queue_lock); + + fpga_queue(card, prm.port, skb, NULL); + + wait_event_timeout(card->param_wq, prm.response, 5 * HZ); + + spin_lock_irq(&card->param_queue_lock); + list_del(&prm.list); + spin_unlock_irq(&card->param_queue_lock); + + skb = prm.response; + + if (!skb) + return -EIO; + + buflen = skb->len; + + /* Sometimes it has a newline, sometimes it doesn't. */ + if (skb->data[buflen - 1] == '\n') + buflen--; + + if (buflen == 2 && !strncmp(skb->data, "OK", 2)) + ret = count; + else if (buflen == 5 && !strncmp(skb->data, "ERROR", 5)) + ret = -EIO; + else { + /* We know we have enough space allocated for this; we allocated + it ourselves */ + skb->data[buflen] = 0; + + dev_warn(&card->dev->dev, "Unexpected parameter response: '%s'\n", + skb->data); + ret = -EIO; + } + kfree_skb(skb); + + return ret; +} + +static char *next_string(struct sk_buff *skb) +{ + int i = 0; + char *this = skb->data; + + for (i = 0; i < skb->len; i++) { + if (this[i] == '\n') { + this[i] = 0; + skb_pull(skb, i + 1); + return this; + } + if (!isprint(this[i])) + return NULL; + } + return NULL; +} + +/* + * Status packet has fields separated by \n, starting with a version number + * for the information therein. Fields are.... + * + * packet version + * RxBitRate (version >= 1) + * TxBitRate (version >= 1) + * State (version >= 1) + * LocalSNRMargin (version >= 1) + * LocalLineAttn (version >= 1) + */ +static int process_status(struct solos_card *card, int port, struct sk_buff *skb) +{ + char *str, *state_str, *snr, *attn; + int ver, rate_up, rate_down, err; + + if (!card->atmdev[port]) + return -ENODEV; + + str = next_string(skb); + if (!str) + return -EIO; + + err = kstrtoint(str, 10, &ver); + if (err) { + dev_warn(&card->dev->dev, "Unexpected status interrupt version\n"); + return err; + } + if (ver < 1) { + dev_warn(&card->dev->dev, "Unexpected status interrupt version %d\n", + ver); + return -EIO; + } + + str = next_string(skb); + if (!str) + return -EIO; + if (!strcmp(str, "ERROR")) { + dev_dbg(&card->dev->dev, "Status packet indicated Solos error on port %d (starting up?)\n", + port); + return 0; + } + + err = kstrtoint(str, 10, &rate_down); + if (err) + return err; + + str = next_string(skb); + if (!str) + return -EIO; + err = kstrtoint(str, 10, &rate_up); + if (err) + return err; + + state_str = next_string(skb); + if (!state_str) + return -EIO; + + /* Anything but 'Showtime' is down */ + if (strcmp(state_str, "Showtime")) { + atm_dev_signal_change(card->atmdev[port], ATM_PHY_SIG_LOST); + dev_info(&card->dev->dev, "Port %d: %s\n", port, state_str); + return 0; + } + + snr = next_string(skb); + if (!snr) + return -EIO; + attn = next_string(skb); + if (!attn) + return -EIO; + + dev_info(&card->dev->dev, "Port %d: %s @%d/%d kb/s%s%s%s%s\n", + port, state_str, rate_down/1000, rate_up/1000, + snr[0]?", SNR ":"", snr, attn[0]?", Attn ":"", attn); + + card->atmdev[port]->link_rate = rate_down / 424; + atm_dev_signal_change(card->atmdev[port], ATM_PHY_SIG_FOUND); + + return 0; +} + +static int process_command(struct solos_card *card, int port, struct sk_buff *skb) +{ + struct solos_param *prm; + unsigned long flags; + int cmdpid; + int found = 0, err; + + if (skb->len < 7) + return 0; + + if (skb->data[0] != 'L' || !isdigit(skb->data[1]) || + !isdigit(skb->data[2]) || !isdigit(skb->data[3]) || + !isdigit(skb->data[4]) || !isdigit(skb->data[5]) || + skb->data[6] != '\n') + return 0; + + err = kstrtoint(&skb->data[1], 10, &cmdpid); + if (err) + return err; + + spin_lock_irqsave(&card->param_queue_lock, flags); + list_for_each_entry(prm, &card->param_queue, list) { + if (prm->port == port && prm->pid == cmdpid) { + prm->response = skb; + skb_pull(skb, 7); + wake_up(&card->param_wq); + found = 1; + break; + } + } + spin_unlock_irqrestore(&card->param_queue_lock, flags); + return found; +} + +static ssize_t console_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); + struct solos_card *card = atmdev->dev_data; + struct sk_buff *skb; + unsigned int len; + + spin_lock_bh(&card->cli_queue_lock); + skb = skb_dequeue(&card->cli_queue[SOLOS_CHAN(atmdev)]); + spin_unlock_bh(&card->cli_queue_lock); + if(skb == NULL) + return sprintf(buf, "No data.\n"); + + len = skb->len; + memcpy(buf, skb->data, len); + + kfree_skb(skb); + return len; +} + +static int send_command(struct solos_card *card, int dev, const char *buf, size_t size) +{ + struct sk_buff *skb; + struct pkt_hdr *header; + + if (size > (BUF_SIZE - sizeof(*header))) { + dev_dbg(&card->dev->dev, "Command is too big. Dropping request\n"); + return 0; + } + skb = alloc_skb(size + sizeof(*header), GFP_ATOMIC); + if (!skb) { + dev_warn(&card->dev->dev, "Failed to allocate sk_buff in send_command()\n"); + return 0; + } + + header = skb_put(skb, sizeof(*header)); + + header->size = cpu_to_le16(size); + header->vpi = cpu_to_le16(0); + header->vci = cpu_to_le16(0); + header->type = cpu_to_le16(PKT_COMMAND); + + skb_put_data(skb, buf, size); + + fpga_queue(card, dev, skb, NULL); + + return 0; +} + +static ssize_t console_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); + struct solos_card *card = atmdev->dev_data; + int err; + + err = send_command(card, SOLOS_CHAN(atmdev), buf, count); + + return err?:count; +} + +struct geos_gpio_attr { + struct device_attribute attr; + int offset; +}; + +#define SOLOS_GPIO_ATTR(_name, _mode, _show, _store, _offset) \ + struct geos_gpio_attr gpio_attr_##_name = { \ + .attr = __ATTR(_name, _mode, _show, _store), \ + .offset = _offset } + +static ssize_t geos_gpio_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct geos_gpio_attr *gattr = container_of(attr, struct geos_gpio_attr, attr); + struct solos_card *card = dev_get_drvdata(dev); + uint32_t data32; + + if (count != 1 && (count != 2 || buf[1] != '\n')) + return -EINVAL; + + spin_lock_irq(&card->param_queue_lock); + data32 = ioread32(card->config_regs + GPIO_STATUS); + if (buf[0] == '1') { + data32 |= 1 << gattr->offset; + iowrite32(data32, card->config_regs + GPIO_STATUS); + } else if (buf[0] == '0') { + data32 &= ~(1 << gattr->offset); + iowrite32(data32, card->config_regs + GPIO_STATUS); + } else { + count = -EINVAL; + } + spin_unlock_irq(&card->param_queue_lock); + return count; +} + +static ssize_t geos_gpio_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct geos_gpio_attr *gattr = container_of(attr, struct geos_gpio_attr, attr); + struct solos_card *card = dev_get_drvdata(dev); + uint32_t data32; + + data32 = ioread32(card->config_regs + GPIO_STATUS); + data32 = (data32 >> gattr->offset) & 1; + + return sprintf(buf, "%d\n", data32); +} + +static ssize_t hardware_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct geos_gpio_attr *gattr = container_of(attr, struct geos_gpio_attr, attr); + struct solos_card *card = dev_get_drvdata(dev); + uint32_t data32; + + data32 = ioread32(card->config_regs + GPIO_STATUS); + switch (gattr->offset) { + case 0: + /* HardwareVersion */ + data32 = data32 & 0x1F; + break; + case 1: + /* HardwareVariant */ + data32 = (data32 >> 5) & 0x0F; + break; + } + return sprintf(buf, "%d\n", data32); +} + +static DEVICE_ATTR_RW(console); + + +#define SOLOS_ATTR_RO(x) static DEVICE_ATTR(x, 0444, solos_param_show, NULL); +#define SOLOS_ATTR_RW(x) static DEVICE_ATTR(x, 0644, solos_param_show, solos_param_store); + +#include "solos-attrlist.c" + +static SOLOS_GPIO_ATTR(GPIO1, 0644, geos_gpio_show, geos_gpio_store, 9); +static SOLOS_GPIO_ATTR(GPIO2, 0644, geos_gpio_show, geos_gpio_store, 10); +static SOLOS_GPIO_ATTR(GPIO3, 0644, geos_gpio_show, geos_gpio_store, 11); +static SOLOS_GPIO_ATTR(GPIO4, 0644, geos_gpio_show, geos_gpio_store, 12); +static SOLOS_GPIO_ATTR(GPIO5, 0644, geos_gpio_show, geos_gpio_store, 13); +static SOLOS_GPIO_ATTR(PushButton, 0444, geos_gpio_show, NULL, 14); +static SOLOS_GPIO_ATTR(HardwareVersion, 0444, hardware_show, NULL, 0); +static SOLOS_GPIO_ATTR(HardwareVariant, 0444, hardware_show, NULL, 1); +#undef SOLOS_ATTR_RO +#undef SOLOS_ATTR_RW + +#define SOLOS_ATTR_RO(x) &dev_attr_##x.attr, +#define SOLOS_ATTR_RW(x) &dev_attr_##x.attr, + +static struct attribute *solos_attrs[] = { +#include "solos-attrlist.c" + NULL +}; + +static const struct attribute_group solos_attr_group = { + .attrs = solos_attrs, + .name = "parameters", +}; + +static struct attribute *gpio_attrs[] = { + &gpio_attr_GPIO1.attr.attr, + &gpio_attr_GPIO2.attr.attr, + &gpio_attr_GPIO3.attr.attr, + &gpio_attr_GPIO4.attr.attr, + &gpio_attr_GPIO5.attr.attr, + &gpio_attr_PushButton.attr.attr, + &gpio_attr_HardwareVersion.attr.attr, + &gpio_attr_HardwareVariant.attr.attr, + NULL +}; + +static const struct attribute_group gpio_attr_group = { + .attrs = gpio_attrs, + .name = "gpio", +}; + +static int flash_upgrade(struct solos_card *card, int chip) +{ + const struct firmware *fw; + const char *fw_name; + int blocksize = 0; + int numblocks = 0; + int offset; + + switch (chip) { + case 0: + fw_name = "solos-FPGA.bin"; + if (card->atmel_flash) + blocksize = ATMEL_FPGA_BLOCK; + else + blocksize = SPI_FLASH_BLOCK; + break; + case 1: + fw_name = "solos-Firmware.bin"; + if (card->atmel_flash) + blocksize = ATMEL_SOLOS_BLOCK; + else + blocksize = SPI_FLASH_BLOCK; + break; + case 2: + if (card->fpga_version > LEGACY_BUFFERS){ + fw_name = "solos-db-FPGA.bin"; + if (card->atmel_flash) + blocksize = ATMEL_FPGA_BLOCK; + else + blocksize = SPI_FLASH_BLOCK; + } else { + dev_info(&card->dev->dev, "FPGA version doesn't support" + " daughter board upgrades\n"); + return -EPERM; + } + break; + case 3: + if (card->fpga_version > LEGACY_BUFFERS){ + fw_name = "solos-Firmware.bin"; + if (card->atmel_flash) + blocksize = ATMEL_SOLOS_BLOCK; + else + blocksize = SPI_FLASH_BLOCK; + } else { + dev_info(&card->dev->dev, "FPGA version doesn't support" + " daughter board upgrades\n"); + return -EPERM; + } + break; + default: + return -ENODEV; + } + + if (request_firmware(&fw, fw_name, &card->dev->dev)) + return -ENOENT; + + dev_info(&card->dev->dev, "Flash upgrade starting\n"); + + /* New FPGAs require driver version before permitting flash upgrades */ + iowrite32(DRIVER_VERSION, card->config_regs + DRIVER_VER); + + numblocks = fw->size / blocksize; + dev_info(&card->dev->dev, "Firmware size: %zd\n", fw->size); + dev_info(&card->dev->dev, "Number of blocks: %d\n", numblocks); + + dev_info(&card->dev->dev, "Changing FPGA to Update mode\n"); + iowrite32(1, card->config_regs + FPGA_MODE); + (void) ioread32(card->config_regs + FPGA_MODE); + + /* Set mode to Chip Erase */ + if(chip == 0 || chip == 2) + dev_info(&card->dev->dev, "Set FPGA Flash mode to FPGA Chip Erase\n"); + if(chip == 1 || chip == 3) + dev_info(&card->dev->dev, "Set FPGA Flash mode to Solos Chip Erase\n"); + iowrite32((chip * 2), card->config_regs + FLASH_MODE); + + + iowrite32(1, card->config_regs + WRITE_FLASH); + wait_event(card->fw_wq, !ioread32(card->config_regs + FLASH_BUSY)); + + for (offset = 0; offset < fw->size; offset += blocksize) { + int i; + + /* Clear write flag */ + iowrite32(0, card->config_regs + WRITE_FLASH); + + /* Set mode to Block Write */ + /* dev_info(&card->dev->dev, "Set FPGA Flash mode to Block Write\n"); */ + iowrite32(((chip * 2) + 1), card->config_regs + FLASH_MODE); + + /* Copy block to buffer, swapping each 16 bits for Atmel flash */ + for(i = 0; i < blocksize; i += 4) { + uint32_t word; + if (card->atmel_flash) + word = swahb32p((uint32_t *)(fw->data + offset + i)); + else + word = *(uint32_t *)(fw->data + offset + i); + if(card->fpga_version > LEGACY_BUFFERS) + iowrite32(word, FLASH_BUF + i); + else + iowrite32(word, RX_BUF(card, 3) + i); + } + + /* Specify block number and then trigger flash write */ + iowrite32(offset / blocksize, card->config_regs + FLASH_BLOCK); + iowrite32(1, card->config_regs + WRITE_FLASH); + wait_event(card->fw_wq, !ioread32(card->config_regs + FLASH_BUSY)); + } + + release_firmware(fw); + iowrite32(0, card->config_regs + WRITE_FLASH); + iowrite32(0, card->config_regs + FPGA_MODE); + iowrite32(0, card->config_regs + FLASH_MODE); + dev_info(&card->dev->dev, "Returning FPGA to Data mode\n"); + return 0; +} + +static irqreturn_t solos_irq(int irq, void *dev_id) +{ + struct solos_card *card = dev_id; + int handled = 1; + + iowrite32(0, card->config_regs + IRQ_CLEAR); + + /* If we're up and running, just kick the tasklet to process TX/RX */ + if (card->atmdev[0]) + tasklet_schedule(&card->tlet); + else + wake_up(&card->fw_wq); + + return IRQ_RETVAL(handled); +} + +static void solos_bh(unsigned long card_arg) +{ + struct solos_card *card = (void *)card_arg; + uint32_t card_flags; + uint32_t rx_done = 0; + int port; + + /* + * Since fpga_tx() is going to need to read the flags under its lock, + * it can return them to us so that we don't have to hit PCI MMIO + * again for the same information + */ + card_flags = fpga_tx(card); + + for (port = 0; port < card->nr_ports; port++) { + if (card_flags & (0x10 << port)) { + struct pkt_hdr _hdr, *header; + struct sk_buff *skb; + struct atm_vcc *vcc; + int size; + + if (card->using_dma) { + skb = card->rx_skb[port]; + card->rx_skb[port] = NULL; + + dma_unmap_single(&card->dev->dev, SKB_CB(skb)->dma_addr, + RX_DMA_SIZE, DMA_FROM_DEVICE); + + header = (void *)skb->data; + size = le16_to_cpu(header->size); + skb_put(skb, size + sizeof(*header)); + skb_pull(skb, sizeof(*header)); + } else { + header = &_hdr; + + rx_done |= 0x10 << port; + + memcpy_fromio(header, RX_BUF(card, port), sizeof(*header)); + + size = le16_to_cpu(header->size); + if (size > (card->buffer_size - sizeof(*header))){ + dev_warn(&card->dev->dev, "Invalid buffer size\n"); + continue; + } + + /* Use netdev_alloc_skb() because it adds NET_SKB_PAD of + * headroom, and ensures we can route packets back out an + * Ethernet interface (for example) without having to + * reallocate. Adding NET_IP_ALIGN also ensures that both + * PPPoATM and PPPoEoBR2684 packets end up aligned. */ + skb = netdev_alloc_skb_ip_align(NULL, size + 1); + if (!skb) { + if (net_ratelimit()) + dev_warn(&card->dev->dev, "Failed to allocate sk_buff for RX\n"); + continue; + } + + memcpy_fromio(skb_put(skb, size), + RX_BUF(card, port) + sizeof(*header), + size); + } + if (atmdebug) { + dev_info(&card->dev->dev, "Received: port %d\n", port); + dev_info(&card->dev->dev, "size: %d VPI: %d VCI: %d\n", + size, le16_to_cpu(header->vpi), + le16_to_cpu(header->vci)); + print_buffer(skb); + } + + switch (le16_to_cpu(header->type)) { + case PKT_DATA: + vcc = find_vcc(card->atmdev[port], le16_to_cpu(header->vpi), + le16_to_cpu(header->vci)); + if (!vcc) { + if (net_ratelimit()) + dev_warn(&card->dev->dev, "Received packet for unknown VPI.VCI %d.%d on port %d\n", + le16_to_cpu(header->vpi), le16_to_cpu(header->vci), + port); + dev_kfree_skb_any(skb); + break; + } + atm_charge(vcc, skb->truesize); + vcc->push(vcc, skb); + atomic_inc(&vcc->stats->rx); + break; + + case PKT_STATUS: + if (process_status(card, port, skb) && + net_ratelimit()) { + dev_warn(&card->dev->dev, "Bad status packet of %d bytes on port %d:\n", skb->len, port); + print_buffer(skb); + } + dev_kfree_skb_any(skb); + break; + + case PKT_COMMAND: + default: /* FIXME: Not really, surely? */ + if (process_command(card, port, skb)) + break; + spin_lock(&card->cli_queue_lock); + if (skb_queue_len(&card->cli_queue[port]) > 10) { + if (net_ratelimit()) + dev_warn(&card->dev->dev, "Dropping console response on port %d\n", + port); + dev_kfree_skb_any(skb); + } else + skb_queue_tail(&card->cli_queue[port], skb); + spin_unlock(&card->cli_queue_lock); + break; + } + } + /* Allocate RX skbs for any ports which need them */ + if (card->using_dma && card->atmdev[port] && + !card->rx_skb[port]) { + /* Unlike the MMIO case (qv) we can't add NET_IP_ALIGN + * here; the FPGA can only DMA to addresses which are + * aligned to 4 bytes. */ + struct sk_buff *skb = dev_alloc_skb(RX_DMA_SIZE); + if (skb) { + SKB_CB(skb)->dma_addr = + dma_map_single(&card->dev->dev, skb->data, + RX_DMA_SIZE, DMA_FROM_DEVICE); + iowrite32(SKB_CB(skb)->dma_addr, + card->config_regs + RX_DMA_ADDR(port)); + card->rx_skb[port] = skb; + } else { + if (net_ratelimit()) + dev_warn(&card->dev->dev, "Failed to allocate RX skb"); + + /* We'll have to try again later */ + tasklet_schedule(&card->tlet); + } + } + } + if (rx_done) + iowrite32(rx_done, card->config_regs + FLAGS_ADDR); + + return; +} + +static struct atm_vcc *find_vcc(struct atm_dev *dev, short vpi, int vci) +{ + struct hlist_head *head; + struct atm_vcc *vcc = NULL; + struct sock *s; + + read_lock(&vcc_sklist_lock); + head = &vcc_hash[vci & (VCC_HTABLE_SIZE -1)]; + sk_for_each(s, head) { + vcc = atm_sk(s); + if (vcc->dev == dev && vcc->vci == vci && + vcc->vpi == vpi && vcc->qos.rxtp.traffic_class != ATM_NONE && + test_bit(ATM_VF_READY, &vcc->flags)) + goto out; + } + vcc = NULL; + out: + read_unlock(&vcc_sklist_lock); + return vcc; +} + +static int popen(struct atm_vcc *vcc) +{ + struct solos_card *card = vcc->dev->dev_data; + struct sk_buff *skb; + struct pkt_hdr *header; + + if (vcc->qos.aal != ATM_AAL5) { + dev_warn(&card->dev->dev, "Unsupported ATM type %d\n", + vcc->qos.aal); + return -EINVAL; + } + + skb = alloc_skb(sizeof(*header), GFP_KERNEL); + if (!skb) { + if (net_ratelimit()) + dev_warn(&card->dev->dev, "Failed to allocate sk_buff in popen()\n"); + return -ENOMEM; + } + header = skb_put(skb, sizeof(*header)); + + header->size = cpu_to_le16(0); + header->vpi = cpu_to_le16(vcc->vpi); + header->vci = cpu_to_le16(vcc->vci); + header->type = cpu_to_le16(PKT_POPEN); + + fpga_queue(card, SOLOS_CHAN(vcc->dev), skb, NULL); + + set_bit(ATM_VF_ADDR, &vcc->flags); + set_bit(ATM_VF_READY, &vcc->flags); + + return 0; +} + +static void pclose(struct atm_vcc *vcc) +{ + struct solos_card *card = vcc->dev->dev_data; + unsigned char port = SOLOS_CHAN(vcc->dev); + struct sk_buff *skb, *tmpskb; + struct pkt_hdr *header; + + /* Remove any yet-to-be-transmitted packets from the pending queue */ + spin_lock_bh(&card->tx_queue_lock); + skb_queue_walk_safe(&card->tx_queue[port], skb, tmpskb) { + if (SKB_CB(skb)->vcc == vcc) { + skb_unlink(skb, &card->tx_queue[port]); + solos_pop(vcc, skb); + } + } + spin_unlock_bh(&card->tx_queue_lock); + + skb = alloc_skb(sizeof(*header), GFP_KERNEL); + if (!skb) { + dev_warn(&card->dev->dev, "Failed to allocate sk_buff in pclose()\n"); + return; + } + header = skb_put(skb, sizeof(*header)); + + header->size = cpu_to_le16(0); + header->vpi = cpu_to_le16(vcc->vpi); + header->vci = cpu_to_le16(vcc->vci); + header->type = cpu_to_le16(PKT_PCLOSE); + + skb_get(skb); + fpga_queue(card, port, skb, NULL); + + if (!wait_event_timeout(card->param_wq, !skb_shared(skb), 5 * HZ)) + dev_warn(&card->dev->dev, + "Timeout waiting for VCC close on port %d\n", port); + + dev_kfree_skb(skb); + + /* Hold up vcc_destroy_socket() (our caller) until solos_bh() in the + tasklet has finished processing any incoming packets (and, more to + the point, using the vcc pointer). */ + tasklet_unlock_wait(&card->tlet); + + clear_bit(ATM_VF_ADDR, &vcc->flags); + + return; +} + +static int print_buffer(struct sk_buff *buf) +{ + int len,i; + char msg[500]; + char item[10]; + + len = buf->len; + for (i = 0; i < len; i++){ + if(i % 8 == 0) + sprintf(msg, "%02X: ", i); + + sprintf(item,"%02X ",*(buf->data + i)); + strcat(msg, item); + if(i % 8 == 7) { + sprintf(item, "\n"); + strcat(msg, item); + printk(KERN_DEBUG "%s", msg); + } + } + if (i % 8 != 0) { + sprintf(item, "\n"); + strcat(msg, item); + printk(KERN_DEBUG "%s", msg); + } + printk(KERN_DEBUG "\n"); + + return 0; +} + +static void fpga_queue(struct solos_card *card, int port, struct sk_buff *skb, + struct atm_vcc *vcc) +{ + int old_len; + unsigned long flags; + + SKB_CB(skb)->vcc = vcc; + + spin_lock_irqsave(&card->tx_queue_lock, flags); + old_len = skb_queue_len(&card->tx_queue[port]); + skb_queue_tail(&card->tx_queue[port], skb); + if (!old_len) + card->tx_mask |= (1 << port); + spin_unlock_irqrestore(&card->tx_queue_lock, flags); + + /* Theoretically we could just schedule the tasklet here, but + that introduces latency we don't want -- it's noticeable */ + if (!old_len) + fpga_tx(card); +} + +static uint32_t fpga_tx(struct solos_card *card) +{ + uint32_t tx_pending, card_flags; + uint32_t tx_started = 0; + struct sk_buff *skb; + struct atm_vcc *vcc; + unsigned char port; + unsigned long flags; + + spin_lock_irqsave(&card->tx_lock, flags); + + card_flags = ioread32(card->config_regs + FLAGS_ADDR); + /* + * The queue lock is required for _writing_ to tx_mask, but we're + * OK to read it here without locking. The only potential update + * that we could race with is in fpga_queue() where it sets a bit + * for a new port... but it's going to call this function again if + * it's doing that, anyway. + */ + tx_pending = card->tx_mask & ~card_flags; + + for (port = 0; tx_pending; tx_pending >>= 1, port++) { + if (tx_pending & 1) { + struct sk_buff *oldskb = card->tx_skb[port]; + if (oldskb) { + dma_unmap_single(&card->dev->dev, SKB_CB(oldskb)->dma_addr, + oldskb->len, DMA_TO_DEVICE); + card->tx_skb[port] = NULL; + } + spin_lock(&card->tx_queue_lock); + skb = skb_dequeue(&card->tx_queue[port]); + if (!skb) + card->tx_mask &= ~(1 << port); + spin_unlock(&card->tx_queue_lock); + + if (skb && !card->using_dma) { + memcpy_toio(TX_BUF(card, port), skb->data, skb->len); + tx_started |= 1 << port; + oldskb = skb; /* We're done with this skb already */ + } else if (skb && card->using_dma) { + unsigned char *data = skb->data; + if ((unsigned long)data & card->dma_alignment) { + data = card->dma_bounce + (BUF_SIZE * port); + memcpy(data, skb->data, skb->len); + } + SKB_CB(skb)->dma_addr = dma_map_single(&card->dev->dev, data, + skb->len, DMA_TO_DEVICE); + card->tx_skb[port] = skb; + iowrite32(SKB_CB(skb)->dma_addr, + card->config_regs + TX_DMA_ADDR(port)); + } + + if (!oldskb) + continue; + + /* Clean up and free oldskb now it's gone */ + if (atmdebug) { + struct pkt_hdr *header = (void *)oldskb->data; + int size = le16_to_cpu(header->size); + + skb_pull(oldskb, sizeof(*header)); + dev_info(&card->dev->dev, "Transmitted: port %d\n", + port); + dev_info(&card->dev->dev, "size: %d VPI: %d VCI: %d\n", + size, le16_to_cpu(header->vpi), + le16_to_cpu(header->vci)); + print_buffer(oldskb); + } + + vcc = SKB_CB(oldskb)->vcc; + + if (vcc) { + atomic_inc(&vcc->stats->tx); + solos_pop(vcc, oldskb); + } else { + dev_kfree_skb_irq(oldskb); + wake_up(&card->param_wq); + } + } + } + /* For non-DMA TX, write the 'TX start' bit for all four ports simultaneously */ + if (tx_started) + iowrite32(tx_started, card->config_regs + FLAGS_ADDR); + + spin_unlock_irqrestore(&card->tx_lock, flags); + return card_flags; +} + +static int psend(struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct solos_card *card = vcc->dev->dev_data; + struct pkt_hdr *header; + int pktlen; + + pktlen = skb->len; + if (pktlen > (BUF_SIZE - sizeof(*header))) { + dev_warn(&card->dev->dev, "Length of PDU is too large. Dropping PDU.\n"); + solos_pop(vcc, skb); + return 0; + } + + if (!skb_clone_writable(skb, sizeof(*header))) { + int expand_by = 0; + int ret; + + if (skb_headroom(skb) < sizeof(*header)) + expand_by = sizeof(*header) - skb_headroom(skb); + + ret = pskb_expand_head(skb, expand_by, 0, GFP_ATOMIC); + if (ret) { + dev_warn(&card->dev->dev, "pskb_expand_head failed.\n"); + solos_pop(vcc, skb); + return ret; + } + } + + header = skb_push(skb, sizeof(*header)); + + /* This does _not_ include the size of the header */ + header->size = cpu_to_le16(pktlen); + header->vpi = cpu_to_le16(vcc->vpi); + header->vci = cpu_to_le16(vcc->vci); + header->type = cpu_to_le16(PKT_DATA); + + fpga_queue(card, SOLOS_CHAN(vcc->dev), skb, vcc); + + return 0; +} + +static const struct atmdev_ops fpga_ops = { + .open = popen, + .close = pclose, + .ioctl = NULL, + .send = psend, + .send_oam = NULL, + .phy_put = NULL, + .phy_get = NULL, + .change_qos = NULL, + .proc_read = NULL, + .owner = THIS_MODULE +}; + +static int fpga_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int err; + uint16_t fpga_ver; + uint8_t major_ver, minor_ver; + uint32_t data32; + struct solos_card *card; + + card = kzalloc(sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + init_waitqueue_head(&card->fw_wq); + init_waitqueue_head(&card->param_wq); + + err = pci_enable_device(dev); + if (err) { + dev_warn(&dev->dev, "Failed to enable PCI device\n"); + goto out; + } + + err = dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); + if (err) { + dev_warn(&dev->dev, "Failed to set 32-bit DMA mask\n"); + goto out; + } + + err = pci_request_regions(dev, "solos"); + if (err) { + dev_warn(&dev->dev, "Failed to request regions\n"); + goto out; + } + + card->config_regs = pci_iomap(dev, 0, CONFIG_RAM_SIZE); + if (!card->config_regs) { + dev_warn(&dev->dev, "Failed to ioremap config registers\n"); + err = -ENOMEM; + goto out_release_regions; + } + card->buffers = pci_iomap(dev, 1, DATA_RAM_SIZE); + if (!card->buffers) { + dev_warn(&dev->dev, "Failed to ioremap data buffers\n"); + err = -ENOMEM; + goto out_unmap_config; + } + + if (reset) { + iowrite32(1, card->config_regs + FPGA_MODE); + ioread32(card->config_regs + FPGA_MODE); + + iowrite32(0, card->config_regs + FPGA_MODE); + ioread32(card->config_regs + FPGA_MODE); + } + + data32 = ioread32(card->config_regs + FPGA_VER); + fpga_ver = (data32 & 0x0000FFFF); + major_ver = ((data32 & 0xFF000000) >> 24); + minor_ver = ((data32 & 0x00FF0000) >> 16); + card->fpga_version = FPGA_VERSION(major_ver,minor_ver); + if (card->fpga_version > LEGACY_BUFFERS) + card->buffer_size = BUF_SIZE; + else + card->buffer_size = OLD_BUF_SIZE; + dev_info(&dev->dev, "Solos FPGA Version %d.%02d svn-%d\n", + major_ver, minor_ver, fpga_ver); + + if (fpga_ver < 37 && (fpga_upgrade || firmware_upgrade || + db_fpga_upgrade || db_firmware_upgrade)) { + dev_warn(&dev->dev, + "FPGA too old; cannot upgrade flash. Use JTAG.\n"); + fpga_upgrade = firmware_upgrade = 0; + db_fpga_upgrade = db_firmware_upgrade = 0; + } + + /* Stopped using Atmel flash after 0.03-38 */ + if (fpga_ver < 39) + card->atmel_flash = 1; + else + card->atmel_flash = 0; + + data32 = ioread32(card->config_regs + PORTS); + card->nr_ports = (data32 & 0x000000FF); + + if (card->fpga_version >= DMA_SUPPORTED) { + pci_set_master(dev); + card->using_dma = 1; + if (1) { /* All known FPGA versions so far */ + card->dma_alignment = 3; + card->dma_bounce = kmalloc_array(card->nr_ports, + BUF_SIZE, GFP_KERNEL); + if (!card->dma_bounce) { + dev_warn(&card->dev->dev, "Failed to allocate DMA bounce buffers\n"); + err = -ENOMEM; + /* Fallback to MMIO doesn't work */ + goto out_unmap_both; + } + } + } else { + card->using_dma = 0; + /* Set RX empty flag for all ports */ + iowrite32(0xF0, card->config_regs + FLAGS_ADDR); + } + + pci_set_drvdata(dev, card); + + tasklet_init(&card->tlet, solos_bh, (unsigned long)card); + spin_lock_init(&card->tx_lock); + spin_lock_init(&card->tx_queue_lock); + spin_lock_init(&card->cli_queue_lock); + spin_lock_init(&card->param_queue_lock); + INIT_LIST_HEAD(&card->param_queue); + + err = request_irq(dev->irq, solos_irq, IRQF_SHARED, + "solos-pci", card); + if (err) { + dev_dbg(&card->dev->dev, "Failed to request interrupt IRQ: %d\n", dev->irq); + goto out_unmap_both; + } + + iowrite32(1, card->config_regs + IRQ_EN_ADDR); + + if (fpga_upgrade) + flash_upgrade(card, 0); + + if (firmware_upgrade) + flash_upgrade(card, 1); + + if (db_fpga_upgrade) + flash_upgrade(card, 2); + + if (db_firmware_upgrade) + flash_upgrade(card, 3); + + err = atm_init(card, &dev->dev); + if (err) + goto out_free_irq; + + if (card->fpga_version >= DMA_SUPPORTED && + sysfs_create_group(&card->dev->dev.kobj, &gpio_attr_group)) + dev_err(&card->dev->dev, "Could not register parameter group for GPIOs\n"); + + return 0; + + out_free_irq: + iowrite32(0, card->config_regs + IRQ_EN_ADDR); + free_irq(dev->irq, card); + tasklet_kill(&card->tlet); + + out_unmap_both: + kfree(card->dma_bounce); + pci_iounmap(dev, card->buffers); + out_unmap_config: + pci_iounmap(dev, card->config_regs); + out_release_regions: + pci_release_regions(dev); + out: + kfree(card); + return err; +} + +static int atm_init(struct solos_card *card, struct device *parent) +{ + int i; + + for (i = 0; i < card->nr_ports; i++) { + struct sk_buff *skb; + struct pkt_hdr *header; + + skb_queue_head_init(&card->tx_queue[i]); + skb_queue_head_init(&card->cli_queue[i]); + + card->atmdev[i] = atm_dev_register("solos-pci", parent, &fpga_ops, -1, NULL); + if (!card->atmdev[i]) { + dev_err(&card->dev->dev, "Could not register ATM device %d\n", i); + atm_remove(card); + return -ENODEV; + } + if (device_create_file(&card->atmdev[i]->class_dev, &dev_attr_console)) + dev_err(&card->dev->dev, "Could not register console for ATM device %d\n", i); + if (sysfs_create_group(&card->atmdev[i]->class_dev.kobj, &solos_attr_group)) + dev_err(&card->dev->dev, "Could not register parameter group for ATM device %d\n", i); + + dev_info(&card->dev->dev, "Registered ATM device %d\n", card->atmdev[i]->number); + + card->atmdev[i]->ci_range.vpi_bits = 8; + card->atmdev[i]->ci_range.vci_bits = 16; + card->atmdev[i]->dev_data = card; + card->atmdev[i]->phy_data = (void *)(unsigned long)i; + atm_dev_signal_change(card->atmdev[i], ATM_PHY_SIG_FOUND); + + skb = alloc_skb(sizeof(*header), GFP_KERNEL); + if (!skb) { + dev_warn(&card->dev->dev, "Failed to allocate sk_buff in atm_init()\n"); + continue; + } + + header = skb_put(skb, sizeof(*header)); + + header->size = cpu_to_le16(0); + header->vpi = cpu_to_le16(0); + header->vci = cpu_to_le16(0); + header->type = cpu_to_le16(PKT_STATUS); + + fpga_queue(card, i, skb, NULL); + } + return 0; +} + +static void atm_remove(struct solos_card *card) +{ + int i; + + for (i = 0; i < card->nr_ports; i++) { + if (card->atmdev[i]) { + struct sk_buff *skb; + + dev_info(&card->dev->dev, "Unregistering ATM device %d\n", card->atmdev[i]->number); + + sysfs_remove_group(&card->atmdev[i]->class_dev.kobj, &solos_attr_group); + atm_dev_deregister(card->atmdev[i]); + + skb = card->rx_skb[i]; + if (skb) { + dma_unmap_single(&card->dev->dev, SKB_CB(skb)->dma_addr, + RX_DMA_SIZE, DMA_FROM_DEVICE); + dev_kfree_skb(skb); + } + skb = card->tx_skb[i]; + if (skb) { + dma_unmap_single(&card->dev->dev, SKB_CB(skb)->dma_addr, + skb->len, DMA_TO_DEVICE); + dev_kfree_skb(skb); + } + while ((skb = skb_dequeue(&card->tx_queue[i]))) + dev_kfree_skb(skb); + + } + } +} + +static void fpga_remove(struct pci_dev *dev) +{ + struct solos_card *card = pci_get_drvdata(dev); + + /* Disable IRQs */ + iowrite32(0, card->config_regs + IRQ_EN_ADDR); + + /* Reset FPGA */ + iowrite32(1, card->config_regs + FPGA_MODE); + (void)ioread32(card->config_regs + FPGA_MODE); + + if (card->fpga_version >= DMA_SUPPORTED) + sysfs_remove_group(&card->dev->dev.kobj, &gpio_attr_group); + + atm_remove(card); + + free_irq(dev->irq, card); + tasklet_kill(&card->tlet); + + kfree(card->dma_bounce); + + /* Release device from reset */ + iowrite32(0, card->config_regs + FPGA_MODE); + (void)ioread32(card->config_regs + FPGA_MODE); + + pci_iounmap(dev, card->buffers); + pci_iounmap(dev, card->config_regs); + + pci_release_regions(dev); + pci_disable_device(dev); + + kfree(card); +} + +static const struct pci_device_id fpga_pci_tbl[] = { + { 0x10ee, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci,fpga_pci_tbl); + +static struct pci_driver fpga_driver = { + .name = "solos", + .id_table = fpga_pci_tbl, + .probe = fpga_probe, + .remove = fpga_remove, +}; + + +static int __init solos_pci_init(void) +{ + BUILD_BUG_ON(sizeof(struct solos_skb_cb) > sizeof(((struct sk_buff *)0)->cb)); + + printk(KERN_INFO "Solos PCI Driver Version %s\n", VERSION); + return pci_register_driver(&fpga_driver); +} + +static void __exit solos_pci_exit(void) +{ + pci_unregister_driver(&fpga_driver); + printk(KERN_INFO "Solos PCI Driver %s Unloaded\n", VERSION); +} + +module_init(solos_pci_init); +module_exit(solos_pci_exit); diff --git a/drivers/atm/suni.c b/drivers/atm/suni.c new file mode 100644 index 000000000..c920a8c52 --- /dev/null +++ b/drivers/atm/suni.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/atm/suni.c - S/UNI PHY driver + * + * Supports the following: + * PMC PM5346 S/UNI LITE + * PMC PM5350 S/UNI 155 ULTRA + * PMC PM5355 S/UNI 622 + */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "suni.h" + + +#if 0 +#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) +#else +#define DPRINTK(format,args...) +#endif + +#define PRIV(dev) ((struct suni_priv *) dev->phy_data) + +#define PUT(val,reg) dev->ops->phy_put(dev,val,SUNI_##reg) +#define GET(reg) dev->ops->phy_get(dev,SUNI_##reg) +#define REG_CHANGE(mask,shift,value,reg) \ + PUT((GET(reg) & ~(mask)) | ((value) << (shift)),reg) + + +static struct timer_list poll_timer; +static struct suni_priv *sunis = NULL; +static DEFINE_SPINLOCK(sunis_lock); + + +#define ADD_LIMITED(s,v) \ + atomic_add((v),&stats->s); \ + if (atomic_read(&stats->s) < 0) atomic_set(&stats->s,INT_MAX); + + +static void suni_hz(struct timer_list *timer) +{ + struct suni_priv *walk; + struct atm_dev *dev; + struct k_sonet_stats *stats; + + for (walk = sunis; walk; walk = walk->next) { + dev = walk->dev; + stats = &walk->sonet_stats; + PUT(0,MRI); /* latch counters */ + udelay(1); + ADD_LIMITED(section_bip,(GET(RSOP_SBL) & 0xff) | + ((GET(RSOP_SBM) & 0xff) << 8)); + ADD_LIMITED(line_bip,(GET(RLOP_LBL) & 0xff) | + ((GET(RLOP_LB) & 0xff) << 8) | + ((GET(RLOP_LBM) & 0xf) << 16)); + ADD_LIMITED(path_bip,(GET(RPOP_PBL) & 0xff) | + ((GET(RPOP_PBM) & 0xff) << 8)); + ADD_LIMITED(line_febe,(GET(RLOP_LFL) & 0xff) | + ((GET(RLOP_LF) & 0xff) << 8) | + ((GET(RLOP_LFM) & 0xf) << 16)); + ADD_LIMITED(path_febe,(GET(RPOP_PFL) & 0xff) | + ((GET(RPOP_PFM) & 0xff) << 8)); + ADD_LIMITED(corr_hcs,GET(RACP_CHEC) & 0xff); + ADD_LIMITED(uncorr_hcs,GET(RACP_UHEC) & 0xff); + ADD_LIMITED(rx_cells,(GET(RACP_RCCL) & 0xff) | + ((GET(RACP_RCC) & 0xff) << 8) | + ((GET(RACP_RCCM) & 7) << 16)); + ADD_LIMITED(tx_cells,(GET(TACP_TCCL) & 0xff) | + ((GET(TACP_TCC) & 0xff) << 8) | + ((GET(TACP_TCCM) & 7) << 16)); + } + if (timer) mod_timer(&poll_timer,jiffies+HZ); +} + + +#undef ADD_LIMITED + + +static int fetch_stats(struct atm_dev *dev,struct sonet_stats __user *arg,int zero) +{ + struct sonet_stats tmp; + int error = 0; + + sonet_copy_stats(&PRIV(dev)->sonet_stats,&tmp); + if (arg) error = copy_to_user(arg,&tmp,sizeof(tmp)); + if (zero && !error) sonet_subtract_stats(&PRIV(dev)->sonet_stats,&tmp); + return error ? -EFAULT : 0; +} + + +#define HANDLE_FLAG(flag,reg,bit) \ + if (todo & flag) { \ + if (set) PUT(GET(reg) | bit,reg); \ + else PUT(GET(reg) & ~bit,reg); \ + todo &= ~flag; \ + } + + +static int change_diag(struct atm_dev *dev,void __user *arg,int set) +{ + int todo; + + if (get_user(todo,(int __user *)arg)) return -EFAULT; + HANDLE_FLAG(SONET_INS_SBIP,TSOP_DIAG,SUNI_TSOP_DIAG_DBIP8); + HANDLE_FLAG(SONET_INS_LBIP,TLOP_DIAG,SUNI_TLOP_DIAG_DBIP); + HANDLE_FLAG(SONET_INS_PBIP,TPOP_CD,SUNI_TPOP_DIAG_DB3); + HANDLE_FLAG(SONET_INS_FRAME,RSOP_CIE,SUNI_RSOP_CIE_FOOF); + HANDLE_FLAG(SONET_INS_LAIS,TSOP_CTRL,SUNI_TSOP_CTRL_LAIS); + HANDLE_FLAG(SONET_INS_PAIS,TPOP_CD,SUNI_TPOP_DIAG_PAIS); + HANDLE_FLAG(SONET_INS_LOS,TSOP_DIAG,SUNI_TSOP_DIAG_DLOS); + HANDLE_FLAG(SONET_INS_HCS,TACP_CS,SUNI_TACP_CS_DHCS); + return put_user(todo,(int __user *)arg) ? -EFAULT : 0; +} + + +#undef HANDLE_FLAG + + +static int get_diag(struct atm_dev *dev,void __user *arg) +{ + int set; + + set = 0; + if (GET(TSOP_DIAG) & SUNI_TSOP_DIAG_DBIP8) set |= SONET_INS_SBIP; + if (GET(TLOP_DIAG) & SUNI_TLOP_DIAG_DBIP) set |= SONET_INS_LBIP; + if (GET(TPOP_CD) & SUNI_TPOP_DIAG_DB3) set |= SONET_INS_PBIP; + /* SONET_INS_FRAME is one-shot only */ + if (GET(TSOP_CTRL) & SUNI_TSOP_CTRL_LAIS) set |= SONET_INS_LAIS; + if (GET(TPOP_CD) & SUNI_TPOP_DIAG_PAIS) set |= SONET_INS_PAIS; + if (GET(TSOP_DIAG) & SUNI_TSOP_DIAG_DLOS) set |= SONET_INS_LOS; + if (GET(TACP_CS) & SUNI_TACP_CS_DHCS) set |= SONET_INS_HCS; + return put_user(set,(int __user *)arg) ? -EFAULT : 0; +} + + +static int set_loopback(struct atm_dev *dev,int mode) +{ + unsigned char control; + int reg, dle, lle; + + if (PRIV(dev)->type == SUNI_MRI_TYPE_PM5355) { + reg = SUNI_MCM; + dle = SUNI_MCM_DLE; + lle = SUNI_MCM_LLE; + } else { + reg = SUNI_MCT; + dle = SUNI_MCT_DLE; + lle = SUNI_MCT_LLE; + } + + control = dev->ops->phy_get(dev, reg) & ~(dle | lle); + switch (mode) { + case ATM_LM_NONE: + break; + case ATM_LM_LOC_PHY: + control |= dle; + break; + case ATM_LM_RMT_PHY: + control |= lle; + break; + default: + return -EINVAL; + } + dev->ops->phy_put(dev, control, reg); + PRIV(dev)->loop_mode = mode; + return 0; +} + +/* + * SONET vs. SDH Configuration + * + * Z0INS (register 0x06): 0 for SONET, 1 for SDH + * ENSS (register 0x3D): 0 for SONET, 1 for SDH + * LEN16 (register 0x28): 0 for SONET, 1 for SDH (n/a for S/UNI 155 QUAD) + * LEN16 (register 0x50): 0 for SONET, 1 for SDH (n/a for S/UNI 155 QUAD) + * S[1:0] (register 0x46): 00 for SONET, 10 for SDH + */ + +static int set_sonet(struct atm_dev *dev) +{ + if (PRIV(dev)->type == SUNI_MRI_TYPE_PM5355) { + PUT(GET(RPOP_RC) & ~SUNI_RPOP_RC_ENSS, RPOP_RC); + PUT(GET(SSTB_CTRL) & ~SUNI_SSTB_CTRL_LEN16, SSTB_CTRL); + PUT(GET(SPTB_CTRL) & ~SUNI_SPTB_CTRL_LEN16, SPTB_CTRL); + } + + REG_CHANGE(SUNI_TPOP_APM_S, SUNI_TPOP_APM_S_SHIFT, + SUNI_TPOP_S_SONET, TPOP_APM); + + return 0; +} + +static int set_sdh(struct atm_dev *dev) +{ + if (PRIV(dev)->type == SUNI_MRI_TYPE_PM5355) { + PUT(GET(RPOP_RC) | SUNI_RPOP_RC_ENSS, RPOP_RC); + PUT(GET(SSTB_CTRL) | SUNI_SSTB_CTRL_LEN16, SSTB_CTRL); + PUT(GET(SPTB_CTRL) | SUNI_SPTB_CTRL_LEN16, SPTB_CTRL); + } + + REG_CHANGE(SUNI_TPOP_APM_S, SUNI_TPOP_APM_S_SHIFT, + SUNI_TPOP_S_SDH, TPOP_APM); + + return 0; +} + + +static int get_framing(struct atm_dev *dev, void __user *arg) +{ + int framing; + unsigned char s; + + + s = (GET(TPOP_APM) & SUNI_TPOP_APM_S) >> SUNI_TPOP_APM_S_SHIFT; + if (s == SUNI_TPOP_S_SONET) + framing = SONET_FRAME_SONET; + else + framing = SONET_FRAME_SDH; + + return put_user(framing, (int __user *) arg) ? -EFAULT : 0; +} + +static int set_framing(struct atm_dev *dev, void __user *arg) +{ + int mode; + + if (get_user(mode, (int __user *) arg)) + return -EFAULT; + + if (mode == SONET_FRAME_SONET) + return set_sonet(dev); + else if (mode == SONET_FRAME_SDH) + return set_sdh(dev); + + return -EINVAL; +} + + +static int suni_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + switch (cmd) { + case SONET_GETSTATZ: + case SONET_GETSTAT: + return fetch_stats(dev, arg, cmd == SONET_GETSTATZ); + case SONET_SETDIAG: + return change_diag(dev,arg,1); + case SONET_CLRDIAG: + return change_diag(dev,arg,0); + case SONET_GETDIAG: + return get_diag(dev,arg); + case SONET_SETFRAMING: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + return set_framing(dev, arg); + case SONET_GETFRAMING: + return get_framing(dev, arg); + case SONET_GETFRSENSE: + return -EINVAL; + case ATM_SETLOOP: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + return set_loopback(dev,(int)(unsigned long)arg); + case ATM_GETLOOP: + return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ? + -EFAULT : 0; + case ATM_QUERYLOOP: + return put_user(ATM_LM_LOC_PHY | ATM_LM_RMT_PHY, + (int __user *) arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + + +static void poll_los(struct atm_dev *dev) +{ + atm_dev_signal_change(dev, + GET(RSOP_SIS) & SUNI_RSOP_SIS_LOSV ? + ATM_PHY_SIG_LOST : ATM_PHY_SIG_FOUND); +} + + +static void suni_int(struct atm_dev *dev) +{ + poll_los(dev); + printk(KERN_NOTICE "%s(itf %d): signal %s\n",dev->type,dev->number, + dev->signal == ATM_PHY_SIG_LOST ? "lost" : "detected again"); +} + + +static int suni_start(struct atm_dev *dev) +{ + unsigned long flags; + int first; + + spin_lock_irqsave(&sunis_lock,flags); + first = !sunis; + PRIV(dev)->next = sunis; + sunis = PRIV(dev); + spin_unlock_irqrestore(&sunis_lock,flags); + memset(&PRIV(dev)->sonet_stats,0,sizeof(struct k_sonet_stats)); + PUT(GET(RSOP_CIE) | SUNI_RSOP_CIE_LOSE,RSOP_CIE); + /* interrupt on loss of signal */ + poll_los(dev); /* ... and clear SUNI interrupts */ + if (dev->signal == ATM_PHY_SIG_LOST) + printk(KERN_WARNING "%s(itf %d): no signal\n",dev->type, + dev->number); + PRIV(dev)->loop_mode = ATM_LM_NONE; + suni_hz(NULL); /* clear SUNI counters */ + (void) fetch_stats(dev,NULL,1); /* clear kernel counters */ + if (first) { + timer_setup(&poll_timer, suni_hz, 0); + poll_timer.expires = jiffies+HZ; +#if 0 +printk(KERN_DEBUG "[u] p=0x%lx,n=0x%lx\n",(unsigned long) poll_timer.list.prev, + (unsigned long) poll_timer.list.next); +#endif + add_timer(&poll_timer); + } + return 0; +} + + +static int suni_stop(struct atm_dev *dev) +{ + struct suni_priv **walk; + unsigned long flags; + + /* let SAR driver worry about stopping interrupts */ + spin_lock_irqsave(&sunis_lock,flags); + for (walk = &sunis; *walk != PRIV(dev); + walk = &PRIV((*walk)->dev)->next); + *walk = PRIV((*walk)->dev)->next; + if (!sunis) del_timer_sync(&poll_timer); + spin_unlock_irqrestore(&sunis_lock,flags); + kfree(PRIV(dev)); + + return 0; +} + + +static const struct atmphy_ops suni_ops = { + .start = suni_start, + .ioctl = suni_ioctl, + .interrupt = suni_int, + .stop = suni_stop, +}; + + +int suni_init(struct atm_dev *dev) +{ + unsigned char mri; + + if (!(dev->phy_data = kmalloc(sizeof(struct suni_priv),GFP_KERNEL))) + return -ENOMEM; + PRIV(dev)->dev = dev; + + mri = GET(MRI); /* reset SUNI */ + PRIV(dev)->type = (mri & SUNI_MRI_TYPE) >> SUNI_MRI_TYPE_SHIFT; + PUT(mri | SUNI_MRI_RESET,MRI); + PUT(mri,MRI); + PUT((GET(MT) & SUNI_MT_DS27_53),MT); /* disable all tests */ + set_sonet(dev); + REG_CHANGE(SUNI_TACP_IUCHP_CLP,0,SUNI_TACP_IUCHP_CLP, + TACP_IUCHP); /* idle cells */ + PUT(SUNI_IDLE_PATTERN,TACP_IUCPOP); + dev->phy = &suni_ops; + + return 0; +} + +EXPORT_SYMBOL(suni_init); + +MODULE_LICENSE("GPL"); diff --git a/drivers/atm/suni.h b/drivers/atm/suni.h new file mode 100644 index 000000000..d28a50d47 --- /dev/null +++ b/drivers/atm/suni.h @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * drivers/atm/suni.h - S/UNI PHY driver + */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + +#ifndef DRIVER_ATM_SUNI_H +#define DRIVER_ATM_SUNI_H + +#include +#include +#include + +/* SUNI registers */ + +#define SUNI_MRI 0x00 /* Master Reset and Identity / Load + Meter */ +#define SUNI_MC 0x01 /* Master Configuration */ +#define SUNI_MIS 0x02 /* Master Interrupt Status */ + /* no 0x03 */ +#define SUNI_MCM 0x04 /* Master Clock Monitor */ +#define SUNI_MCT 0x05 /* Master Control */ +#define SUNI_CSCS 0x06 /* Clock Synthesis Control and Status */ +#define SUNI_CRCS 0x07 /* Clock Recovery Control and Status */ + /* 0x08-0x0F reserved */ +#define SUNI_RSOP_CIE 0x10 /* RSOP Control/Interrupt Enable */ +#define SUNI_RSOP_SIS 0x11 /* RSOP Status/Interrupt Status */ +#define SUNI_RSOP_SBL 0x12 /* RSOP Section BIP-8 LSB */ +#define SUNI_RSOP_SBM 0x13 /* RSOP Section BIP-8 MSB */ +#define SUNI_TSOP_CTRL 0x14 /* TSOP Control */ +#define SUNI_TSOP_DIAG 0x15 /* TSOP Diagnostic */ + /* 0x16-0x17 reserved */ +#define SUNI_RLOP_CS 0x18 /* RLOP Control/Status */ +#define SUNI_RLOP_IES 0x19 /* RLOP Interrupt Enable/Status */ +#define SUNI_RLOP_LBL 0x1A /* RLOP Line BIP-8/24 LSB */ +#define SUNI_RLOP_LB 0x1B /* RLOP Line BIP-8/24 */ +#define SUNI_RLOP_LBM 0x1C /* RLOP Line BIP-8/24 MSB */ +#define SUNI_RLOP_LFL 0x1D /* RLOP Line FEBE LSB */ +#define SUNI_RLOP_LF 0x1E /* RLOP Line FEBE */ +#define SUNI_RLOP_LFM 0x1F /* RLOP Line FEBE MSB */ +#define SUNI_TLOP_CTRL 0x20 /* TLOP Control */ +#define SUNI_TLOP_DIAG 0x21 /* TLOP Diagnostic */ + /* 0x22-0x27 reserved */ +#define SUNI_SSTB_CTRL 0x28 +#define SUNI_RPOP_SC 0x30 /* RPOP Status/Control */ +#define SUNI_RPOP_IS 0x31 /* RPOP Interrupt Status */ + /* 0x32 reserved */ +#define SUNI_RPOP_IE 0x33 /* RPOP Interrupt Enable */ + /* 0x34-0x36 reserved */ +#define SUNI_RPOP_PSL 0x37 /* RPOP Path Signal Label */ +#define SUNI_RPOP_PBL 0x38 /* RPOP Path BIP-8 LSB */ +#define SUNI_RPOP_PBM 0x39 /* RPOP Path BIP-8 MSB */ +#define SUNI_RPOP_PFL 0x3A /* RPOP Path FEBE LSB */ +#define SUNI_RPOP_PFM 0x3B /* RPOP Path FEBE MSB */ + /* 0x3C reserved */ +#define SUNI_RPOP_PBC 0x3D /* RPOP Path BIP-8 Configuration */ +#define SUNI_RPOP_RC 0x3D /* RPOP Ring Control (PM5355) */ + /* 0x3E-0x3F reserved */ +#define SUNI_TPOP_CD 0x40 /* TPOP Control/Diagnostic */ +#define SUNI_TPOP_PC 0x41 /* TPOP Pointer Control */ + /* 0x42-0x44 reserved */ +#define SUNI_TPOP_APL 0x45 /* TPOP Arbitrary Pointer LSB */ +#define SUNI_TPOP_APM 0x46 /* TPOP Arbitrary Pointer MSB */ + /* 0x47 reserved */ +#define SUNI_TPOP_PSL 0x48 /* TPOP Path Signal Label */ +#define SUNI_TPOP_PS 0x49 /* TPOP Path Status */ + /* 0x4A-0x4F reserved */ +#define SUNI_RACP_CS 0x50 /* RACP Control/Status */ +#define SUNI_RACP_IES 0x51 /* RACP Interrupt Enable/Status */ +#define SUNI_RACP_MHP 0x52 /* RACP Match Header Pattern */ +#define SUNI_RACP_MHM 0x53 /* RACP Match Header Mask */ +#define SUNI_RACP_CHEC 0x54 /* RACP Correctable HCS Error Count */ +#define SUNI_RACP_UHEC 0x55 /* RACP Uncorrectable HCS Err Count */ +#define SUNI_RACP_RCCL 0x56 /* RACP Receive Cell Counter LSB */ +#define SUNI_RACP_RCC 0x57 /* RACP Receive Cell Counter */ +#define SUNI_RACP_RCCM 0x58 /* RACP Receive Cell Counter MSB */ +#define SUNI_RACP_CFG 0x59 /* RACP Configuration */ + /* 0x5A-0x5F reserved */ +#define SUNI_TACP_CS 0x60 /* TACP Control/Status */ +#define SUNI_TACP_IUCHP 0x61 /* TACP Idle/Unassigned Cell Hdr Pat */ +#define SUNI_TACP_IUCPOP 0x62 /* TACP Idle/Unassigned Cell Payload + Octet Pattern */ +#define SUNI_TACP_FIFO 0x63 /* TACP FIFO Configuration */ +#define SUNI_TACP_TCCL 0x64 /* TACP Transmit Cell Counter LSB */ +#define SUNI_TACP_TCC 0x65 /* TACP Transmit Cell Counter */ +#define SUNI_TACP_TCCM 0x66 /* TACP Transmit Cell Counter MSB */ +#define SUNI_TACP_CFG 0x67 /* TACP Configuration */ +#define SUNI_SPTB_CTRL 0x68 /* SPTB Control */ + /* 0x69-0x7F reserved */ +#define SUNI_MT 0x80 /* Master Test */ + /* 0x81-0xFF reserved */ + +/* SUNI register values */ + + +/* MRI is reg 0 */ +#define SUNI_MRI_ID 0x0f /* R, SUNI revision number */ +#define SUNI_MRI_ID_SHIFT 0 +#define SUNI_MRI_TYPE 0x70 /* R, SUNI type (lite is 011) */ +#define SUNI_MRI_TYPE_SHIFT 4 +#define SUNI_MRI_TYPE_PM5346 0x3 /* S/UNI 155 LITE */ +#define SUNI_MRI_TYPE_PM5347 0x4 /* S/UNI 155 PLUS */ +#define SUNI_MRI_TYPE_PM5350 0x7 /* S/UNI 155 ULTRA */ +#define SUNI_MRI_TYPE_PM5355 0x1 /* S/UNI 622 */ +#define SUNI_MRI_RESET 0x80 /* RW, reset & power down chip + 0: normal operation + 1: reset & low power */ + +/* MCM is reg 0x4 */ +#define SUNI_MCM_LLE 0x20 /* line loopback (PM5355) */ +#define SUNI_MCM_DLE 0x10 /* diagnostic loopback (PM5355) */ + +/* MCT is reg 5 */ +#define SUNI_MCT_LOOPT 0x01 /* RW, timing source, 0: from + TRCLK+/- */ +#define SUNI_MCT_DLE 0x02 /* RW, diagnostic loopback */ +#define SUNI_MCT_LLE 0x04 /* RW, line loopback */ +#define SUNI_MCT_FIXPTR 0x20 /* RW, disable transmit payload pointer + adjustments + 0: payload ptr controlled by TPOP + ptr control reg + 1: payload pointer fixed at 522 */ +#define SUNI_MCT_LCDV 0x40 /* R, loss of cell delineation */ +#define SUNI_MCT_LCDE 0x80 /* RW, loss of cell delineation + interrupt (1: on) */ +/* RSOP_CIE is reg 0x10 */ +#define SUNI_RSOP_CIE_OOFE 0x01 /* RW, enable interrupt on frame alarm + state change */ +#define SUNI_RSOP_CIE_LOFE 0x02 /* RW, enable interrupt on loss of + frame state change */ +#define SUNI_RSOP_CIE_LOSE 0x04 /* RW, enable interrupt on loss of + signal state change */ +#define SUNI_RSOP_CIE_BIPEE 0x08 /* RW, enable interrupt on section + BIP-8 error (B1) */ +#define SUNI_RSOP_CIE_FOOF 0x20 /* W, force RSOP out of frame at next + boundary */ +#define SUNI_RSOP_CIE_DDS 0x40 /* RW, disable scrambling */ + +/* RSOP_SIS is reg 0x11 */ +#define SUNI_RSOP_SIS_OOFV 0x01 /* R, out of frame */ +#define SUNI_RSOP_SIS_LOFV 0x02 /* R, loss of frame */ +#define SUNI_RSOP_SIS_LOSV 0x04 /* R, loss of signal */ +#define SUNI_RSOP_SIS_OOFI 0x08 /* R, out of frame interrupt */ +#define SUNI_RSOP_SIS_LOFI 0x10 /* R, loss of frame interrupt */ +#define SUNI_RSOP_SIS_LOSI 0x20 /* R, loss of signal interrupt */ +#define SUNI_RSOP_SIS_BIPEI 0x40 /* R, section BIP-8 interrupt */ + +/* TSOP_CTRL is reg 0x14 */ +#define SUNI_TSOP_CTRL_LAIS 0x01 /* insert alarm indication signal */ +#define SUNI_TSOP_CTRL_DS 0x40 /* disable scrambling */ + +/* TSOP_DIAG is reg 0x15 */ +#define SUNI_TSOP_DIAG_DFP 0x01 /* insert single bit error cont. */ +#define SUNI_TSOP_DIAG_DBIP8 0x02 /* insert section BIP err (cont) */ +#define SUNI_TSOP_DIAG_DLOS 0x04 /* set line to zero (loss of signal) */ + +/* TLOP_DIAG is reg 0x21 */ +#define SUNI_TLOP_DIAG_DBIP 0x01 /* insert line BIP err (continuously) */ + +/* SSTB_CTRL is reg 0x28 */ +#define SUNI_SSTB_CTRL_LEN16 0x01 /* path trace message length bit */ + +/* RPOP_RC is reg 0x3D (PM5355) */ +#define SUNI_RPOP_RC_ENSS 0x40 /* enable size bit */ + +/* TPOP_DIAG is reg 0x40 */ +#define SUNI_TPOP_DIAG_PAIS 0x01 /* insert STS path alarm ind (cont) */ +#define SUNI_TPOP_DIAG_DB3 0x02 /* insert path BIP err (continuously) */ + +/* TPOP_APM is reg 0x46 */ +#define SUNI_TPOP_APM_APTR 0x03 /* RW, arbitrary pointer, upper 2 + bits */ +#define SUNI_TPOP_APM_APTR_SHIFT 0 +#define SUNI_TPOP_APM_S 0x0c /* RW, "unused" bits of payload + pointer */ +#define SUNI_TPOP_APM_S_SHIFT 2 +#define SUNI_TPOP_APM_NDF 0xf0 /* RW, NDF bits */ +#define SUNI_TPOP_APM_NDF_SHIFT 4 + +#define SUNI_TPOP_S_SONET 0 /* set S bits to 00 */ +#define SUNI_TPOP_S_SDH 2 /* set S bits to 10 */ + +/* RACP_IES is reg 0x51 */ +#define SUNI_RACP_IES_FOVRI 0x02 /* R, FIFO overrun */ +#define SUNI_RACP_IES_UHCSI 0x04 /* R, uncorrectable HCS error */ +#define SUNI_RACP_IES_CHCSI 0x08 /* R, correctable HCS error */ +#define SUNI_RACP_IES_OOCDI 0x10 /* R, change of cell delineation + state */ +#define SUNI_RACP_IES_FIFOE 0x20 /* RW, enable FIFO overrun interrupt */ +#define SUNI_RACP_IES_HCSE 0x40 /* RW, enable HCS error interrupt */ +#define SUNI_RACP_IES_OOCDE 0x80 /* RW, enable cell delineation state + change interrupt */ + +/* TACP_CS is reg 0x60 */ +#define SUNI_TACP_CS_FIFORST 0x01 /* RW, reset transmit FIFO (sticky) */ +#define SUNI_TACP_CS_DSCR 0x02 /* RW, disable payload scrambling */ +#define SUNI_TACP_CS_HCAADD 0x04 /* RW, add coset polynomial to HCS */ +#define SUNI_TACP_CS_DHCS 0x10 /* RW, insert HCS errors */ +#define SUNI_TACP_CS_FOVRI 0x20 /* R, FIFO overrun */ +#define SUNI_TACP_CS_TSOCI 0x40 /* R, TSOC input high */ +#define SUNI_TACP_CS_FIFOE 0x80 /* RW, enable FIFO overrun interrupt */ + +/* TACP_IUCHP is reg 0x61 */ +#define SUNI_TACP_IUCHP_CLP 0x01 /* RW, 8th bit of 4th octet of i/u + pattern */ +#define SUNI_TACP_IUCHP_PTI 0x0e /* RW, 5th-7th bits of 4th octet of i/u + pattern */ +#define SUNI_TACP_IUCHP_PTI_SHIFT 1 +#define SUNI_TACP_IUCHP_GFC 0xf0 /* RW, 1st-4th bits of 1st octet of i/u + pattern */ +#define SUNI_TACP_IUCHP_GFC_SHIFT 4 + +/* SPTB_CTRL is reg 0x68 */ +#define SUNI_SPTB_CTRL_LEN16 0x01 /* path trace message length */ + +/* MT is reg 0x80 */ +#define SUNI_MT_HIZIO 0x01 /* RW, all but data bus & MP interface + tri-state */ +#define SUNI_MT_HIZDATA 0x02 /* W, also tri-state data bus */ +#define SUNI_MT_IOTST 0x04 /* RW, enable test mode */ +#define SUNI_MT_DBCTRL 0x08 /* W, control data bus by CSB pin */ +#define SUNI_MT_PMCTST 0x10 /* W, PMC test mode */ +#define SUNI_MT_DS27_53 0x80 /* RW, select between 8- or 16- bit */ + + +#define SUNI_IDLE_PATTERN 0x6a /* idle pattern */ + + +#ifdef __KERNEL__ +struct suni_priv { + struct k_sonet_stats sonet_stats; /* link diagnostics */ + int loop_mode; /* loopback mode */ + int type; /* phy type */ + struct atm_dev *dev; /* device back-pointer */ + struct suni_priv *next; /* next SUNI */ +}; + +int suni_init(struct atm_dev *dev); +#endif + +#endif diff --git a/drivers/atm/tonga.h b/drivers/atm/tonga.h new file mode 100644 index 000000000..771b3f952 --- /dev/null +++ b/drivers/atm/tonga.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/tonga.h - Efficient Networks Tonga (PCI bridge) declarations */ + +/* Written 1995 by Werner Almesberger, EPFL LRC */ + + +#ifndef DRIVER_ATM_TONGA_H +#define DRIVER_ATM_TONGA_H + +#define PCI_TONGA_CTRL 0x60 /* control register */ + +#define END_SWAP_DMA 0x80 /* endian swap on DMA */ +#define END_SWAP_BYTE 0x40 /* endian swap on slave byte accesses */ +#define END_SWAP_WORD 0x20 /* endian swap on slave word accesses */ +#define SEPROM_MAGIC 0x0c /* obscure required pattern (ASIC only) */ +#define SEPROM_DATA 0x02 /* serial EEPROM data (ASIC only) */ +#define SEPROM_CLK 0x01 /* serial EEPROM clock (ASIC only) */ + +#define SEPROM_ESI_BASE 64 /* start of ESI in serial EEPROM */ + +#endif diff --git a/drivers/atm/uPD98401.h b/drivers/atm/uPD98401.h new file mode 100644 index 000000000..f766a5ef0 --- /dev/null +++ b/drivers/atm/uPD98401.h @@ -0,0 +1,293 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/uPD98401.h - NEC uPD98401 (SAR) declarations */ + +/* Written 1995 by Werner Almesberger, EPFL LRC */ + + +#ifndef DRIVERS_ATM_uPD98401_H +#define DRIVERS_ATM_uPD98401_H + + +#define MAX_CRAM_SIZE (1 << 18) /* 2^18 words */ +#define RAM_INCREMENT 1024 /* check in 4 kB increments */ + +#define uPD98401_PORTS 0x24 /* probably more ? */ + + +/* + * Commands + */ + +#define uPD98401_OPEN_CHAN 0x20000000 /* open channel */ +#define uPD98401_CHAN_ADDR 0x0003fff8 /* channel address */ +#define uPD98401_CHAN_ADDR_SHIFT 3 +#define uPD98401_CLOSE_CHAN 0x24000000 /* close channel */ +#define uPD98401_CHAN_RT 0x02000000 /* RX/TX (0 TX, 1 RX) */ +#define uPD98401_DEACT_CHAN 0x28000000 /* deactivate channel */ +#define uPD98401_TX_READY 0x30000000 /* TX ready */ +#define uPD98401_ADD_BAT 0x34000000 /* add batches */ +#define uPD98401_POOL 0x000f0000 /* pool number */ +#define uPD98401_POOL_SHIFT 16 +#define uPD98401_POOL_NUMBAT 0x0000ffff /* number of batches */ +#define uPD98401_NOP 0x3f000000 /* NOP */ +#define uPD98401_IND_ACC 0x00000000 /* Indirect Access */ +#define uPD98401_IA_RW 0x10000000 /* Read/Write (0 W, 1 R) */ +#define uPD98401_IA_B3 0x08000000 /* Byte select, 1 enable */ +#define uPD98401_IA_B2 0x04000000 +#define uPD98401_IA_B1 0x02000000 +#define uPD98401_IA_B0 0x01000000 +#define uPD98401_IA_BALL 0x0f000000 /* whole longword */ +#define uPD98401_IA_TGT 0x000c0000 /* Target */ +#define uPD98401_IA_TGT_SHIFT 18 +#define uPD98401_IA_TGT_CM 0 /* - Control Memory */ +#define uPD98401_IA_TGT_SAR 1 /* - uPD98401 registers */ +#define uPD98401_IA_TGT_PHY 3 /* - PHY device */ +#define uPD98401_IA_ADDR 0x0003ffff + +/* + * Command Register Status + */ + +#define uPD98401_BUSY 0x80000000 /* SAR is busy */ +#define uPD98401_LOCKED 0x40000000 /* SAR is locked by other CPU */ + +/* + * Indications + */ + +/* Normal (AAL5) Receive Indication */ +#define uPD98401_AAL5_UINFO 0xffff0000 /* user-supplied information */ +#define uPD98401_AAL5_UINFO_SHIFT 16 +#define uPD98401_AAL5_SIZE 0x0000ffff /* PDU size (in _CELLS_ !!) */ +#define uPD98401_AAL5_CHAN 0x7fff0000 /* Channel number */ +#define uPD98401_AAL5_CHAN_SHIFT 16 +#define uPD98401_AAL5_ERR 0x00008000 /* Error indication */ +#define uPD98401_AAL5_CI 0x00004000 /* Congestion Indication */ +#define uPD98401_AAL5_CLP 0x00002000 /* CLP (>= 1 cell had CLP=1) */ +#define uPD98401_AAL5_ES 0x00000f00 /* Error Status */ +#define uPD98401_AAL5_ES_SHIFT 8 +#define uPD98401_AAL5_ES_NONE 0 /* No error */ +#define uPD98401_AAL5_ES_FREE 1 /* Receiver free buf underflow */ +#define uPD98401_AAL5_ES_FIFO 2 /* Receiver FIFO overrun */ +#define uPD98401_AAL5_ES_TOOBIG 3 /* Maximum length violation */ +#define uPD98401_AAL5_ES_CRC 4 /* CRC error */ +#define uPD98401_AAL5_ES_ABORT 5 /* User abort */ +#define uPD98401_AAL5_ES_LENGTH 6 /* Length violation */ +#define uPD98401_AAL5_ES_T1 7 /* T1 error (timeout) */ +#define uPD98401_AAL5_ES_DEACT 8 /* Deactivated with DEACT_CHAN */ +#define uPD98401_AAL5_POOL 0x0000001f /* Free buffer pool number */ + +/* Raw Cell Indication */ +#define uPD98401_RAW_UINFO uPD98401_AAL5_UINFO +#define uPD98401_RAW_UINFO_SHIFT uPD98401_AAL5_UINFO_SHIFT +#define uPD98401_RAW_HEC 0x000000ff /* HEC */ +#define uPD98401_RAW_CHAN uPD98401_AAL5_CHAN +#define uPD98401_RAW_CHAN_SHIFT uPD98401_AAL5_CHAN_SHIFT + +/* Transmit Indication */ +#define uPD98401_TXI_CONN 0x7fff0000 /* Connection Number */ +#define uPD98401_TXI_CONN_SHIFT 16 +#define uPD98401_TXI_ACTIVE 0x00008000 /* Channel remains active */ +#define uPD98401_TXI_PQP 0x00007fff /* Packet Queue Pointer */ + +/* + * Directly Addressable Registers + */ + +#define uPD98401_GMR 0x00 /* General Mode Register */ +#define uPD98401_GSR 0x01 /* General Status Register */ +#define uPD98401_IMR 0x02 /* Interrupt Mask Register */ +#define uPD98401_RQU 0x03 /* Receive Queue Underrun */ +#define uPD98401_RQA 0x04 /* Receive Queue Alert */ +#define uPD98401_ADDR 0x05 /* Last Burst Address */ +#define uPD98401_VER 0x06 /* Version Number */ +#define uPD98401_SWR 0x07 /* Software Reset */ +#define uPD98401_CMR 0x08 /* Command Register */ +#define uPD98401_CMR_L 0x09 /* Command Register and Lock/Unlock */ +#define uPD98401_CER 0x0a /* Command Extension Register */ +#define uPD98401_CER_L 0x0b /* Command Ext Reg and Lock/Unlock */ + +#define uPD98401_MSH(n) (0x10+(n)) /* Mailbox n Start Address High */ +#define uPD98401_MSL(n) (0x14+(n)) /* Mailbox n Start Address High */ +#define uPD98401_MBA(n) (0x18+(n)) /* Mailbox n Bottom Address */ +#define uPD98401_MTA(n) (0x1c+(n)) /* Mailbox n Tail Address */ +#define uPD98401_MWA(n) (0x20+(n)) /* Mailbox n Write Address */ + +/* GMR is at 0x00 */ +#define uPD98401_GMR_ONE 0x80000000 /* Must be set to one */ +#define uPD98401_GMR_SLM 0x40000000 /* Address mode (0 word, 1 byte) */ +#define uPD98401_GMR_CPE 0x00008000 /* Control Memory Parity Enable */ +#define uPD98401_GMR_LP 0x00004000 /* Loopback */ +#define uPD98401_GMR_WA 0x00002000 /* Early Bus Write Abort/RDY */ +#define uPD98401_GMR_RA 0x00001000 /* Early Read Abort/RDY */ +#define uPD98401_GMR_SZ 0x00000f00 /* Burst Size Enable */ +#define uPD98401_BURST16 0x00000800 /* 16-word burst */ +#define uPD98401_BURST8 0x00000400 /* 8-word burst */ +#define uPD98401_BURST4 0x00000200 /* 4-word burst */ +#define uPD98401_BURST2 0x00000100 /* 2-word burst */ +#define uPD98401_GMR_AD 0x00000080 /* Address (burst resolution) Disable */ +#define uPD98401_GMR_BO 0x00000040 /* Byte Order (0 little, 1 big) */ +#define uPD98401_GMR_PM 0x00000020 /* Bus Parity Mode (0 byte, 1 word)*/ +#define uPD98401_GMR_PC 0x00000010 /* Bus Parity Control (0even,1odd) */ +#define uPD98401_GMR_BPE 0x00000008 /* Bus Parity Enable */ +#define uPD98401_GMR_DR 0x00000004 /* Receive Drop Mode (0drop,1don't)*/ +#define uPD98401_GMR_SE 0x00000002 /* Shapers Enable */ +#define uPD98401_GMR_RE 0x00000001 /* Receiver Enable */ + +/* GSR is at 0x01, IMR is at 0x02 */ +#define uPD98401_INT_PI 0x80000000 /* PHY interrupt */ +#define uPD98401_INT_RQA 0x40000000 /* Receive Queue Alert */ +#define uPD98401_INT_RQU 0x20000000 /* Receive Queue Underrun */ +#define uPD98401_INT_RD 0x10000000 /* Receiver Deactivated */ +#define uPD98401_INT_SPE 0x08000000 /* System Parity Error */ +#define uPD98401_INT_CPE 0x04000000 /* Control Memory Parity Error */ +#define uPD98401_INT_SBE 0x02000000 /* System Bus Error */ +#define uPD98401_INT_IND 0x01000000 /* Initialization Done */ +#define uPD98401_INT_RCR 0x0000ff00 /* Raw Cell Received */ +#define uPD98401_INT_RCR_SHIFT 8 +#define uPD98401_INT_MF 0x000000f0 /* Mailbox Full */ +#define uPD98401_INT_MF_SHIFT 4 +#define uPD98401_INT_MM 0x0000000f /* Mailbox Modified */ + +/* VER is at 0x06 */ +#define uPD98401_MAJOR 0x0000ff00 /* Major revision */ +#define uPD98401_MAJOR_SHIFT 8 +#define uPD98401_MINOR 0x000000ff /* Minor revision */ + +/* + * Indirectly Addressable Registers + */ + +#define uPD98401_IM(n) (0x40000+(n)) /* Scheduler n I and M */ +#define uPD98401_X(n) (0x40010+(n)) /* Scheduler n X */ +#define uPD98401_Y(n) (0x40020+(n)) /* Scheduler n Y */ +#define uPD98401_PC(n) (0x40030+(n)) /* Scheduler n P, C, p and c */ +#define uPD98401_PS(n) (0x40040+(n)) /* Scheduler n priority and status */ + +/* IM contents */ +#define uPD98401_IM_I 0xff000000 /* I */ +#define uPD98401_IM_I_SHIFT 24 +#define uPD98401_IM_M 0x00ffffff /* M */ + +/* PC contents */ +#define uPD98401_PC_P 0xff000000 /* P */ +#define uPD98401_PC_P_SHIFT 24 +#define uPD98401_PC_C 0x00ff0000 /* C */ +#define uPD98401_PC_C_SHIFT 16 +#define uPD98401_PC_p 0x0000ff00 /* p */ +#define uPD98401_PC_p_SHIFT 8 +#define uPD98401_PC_c 0x000000ff /* c */ + +/* PS contents */ +#define uPD98401_PS_PRIO 0xf0 /* Priority level (0 high, 15 low) */ +#define uPD98401_PS_PRIO_SHIFT 4 +#define uPD98401_PS_S 0x08 /* Scan - must be 0 (internal) */ +#define uPD98401_PS_R 0x04 /* Round Robin (internal) */ +#define uPD98401_PS_A 0x02 /* Active (internal) */ +#define uPD98401_PS_E 0x01 /* Enabled */ + +#define uPD98401_TOS 0x40100 /* Top of Stack Control Memory Address */ +#define uPD98401_SMA 0x40200 /* Shapers Control Memory Start Address */ +#define uPD98401_PMA 0x40201 /* Receive Pool Control Memory Start Address */ +#define uPD98401_T1R 0x40300 /* T1 Register */ +#define uPD98401_VRR 0x40301 /* VPI/VCI Reduction Register/Recv. Shutdown */ +#define uPD98401_TSR 0x40302 /* Time-Stamp Register */ + +/* VRR is at 0x40301 */ +#define uPD98401_VRR_SDM 0x80000000 /* Shutdown Mode */ +#define uPD98401_VRR_SHIFT 0x000f0000 /* VPI/VCI Shift */ +#define uPD98401_VRR_SHIFT_SHIFT 16 +#define uPD98401_VRR_MASK 0x0000ffff /* VPI/VCI mask */ + +/* + * TX packet descriptor + */ + +#define uPD98401_TXPD_SIZE 16 /* descriptor size (in bytes) */ + +#define uPD98401_TXPD_V 0x80000000 /* Valid bit */ +#define uPD98401_TXPD_DP 0x40000000 /* Descriptor (1) or Pointer (0) */ +#define uPD98401_TXPD_SM 0x20000000 /* Single (1) or Multiple (0) */ +#define uPD98401_TXPD_CLPM 0x18000000 /* CLP mode */ +#define uPD98401_CLPM_0 0 /* 00 CLP = 0 */ +#define uPD98401_CLPM_1 3 /* 11 CLP = 1 */ +#define uPD98401_CLPM_LAST 1 /* 01 CLP unless last cell */ +#define uPD98401_TXPD_CLPM_SHIFT 27 +#define uPD98401_TXPD_PTI 0x07000000 /* PTI pattern */ +#define uPD98401_TXPD_PTI_SHIFT 24 +#define uPD98401_TXPD_GFC 0x00f00000 /* GFC pattern */ +#define uPD98401_TXPD_GFC_SHIFT 20 +#define uPD98401_TXPD_C10 0x00040000 /* insert CRC-10 */ +#define uPD98401_TXPD_AAL5 0x00020000 /* AAL5 processing */ +#define uPD98401_TXPD_MB 0x00010000 /* TX mailbox number */ +#define uPD98401_TXPD_UU 0x0000ff00 /* CPCS-UU */ +#define uPD98401_TXPD_UU_SHIFT 8 +#define uPD98401_TXPD_CPI 0x000000ff /* CPI */ + +/* + * TX buffer descriptor + */ + +#define uPD98401_TXBD_SIZE 8 /* descriptor size (in bytes) */ + +#define uPD98401_TXBD_LAST 0x80000000 /* last buffer in packet */ + +/* + * TX VC table + */ + +/* 1st word has the same structure as in a TX packet descriptor */ +#define uPD98401_TXVC_L 0x80000000 /* last buffer */ +#define uPD98401_TXVC_SHP 0x0f000000 /* shaper number */ +#define uPD98401_TXVC_SHP_SHIFT 24 +#define uPD98401_TXVC_VPI 0x00ff0000 /* VPI */ +#define uPD98401_TXVC_VPI_SHIFT 16 +#define uPD98401_TXVC_VCI 0x0000ffff /* VCI */ +#define uPD98401_TXVC_QRP 6 /* Queue Read Pointer is in word 6 */ + +/* + * RX free buffer pools descriptor + */ + +#define uPD98401_RXFP_ALERT 0x70000000 /* low water mark */ +#define uPD98401_RXFP_ALERT_SHIFT 28 +#define uPD98401_RXFP_BFSZ 0x0f000000 /* buffer size, 64*2^n */ +#define uPD98401_RXFP_BFSZ_SHIFT 24 +#define uPD98401_RXFP_BTSZ 0x00ff0000 /* batch size, n+1 */ +#define uPD98401_RXFP_BTSZ_SHIFT 16 +#define uPD98401_RXFP_REMAIN 0x0000ffff /* remaining batches in pool */ + +/* + * RX VC table + */ + +#define uPD98401_RXVC_BTSZ 0xff000000 /* remaining free buffers in batch */ +#define uPD98401_RXVC_BTSZ_SHIFT 24 +#define uPD98401_RXVC_MB 0x00200000 /* RX mailbox number */ +#define uPD98401_RXVC_POOL 0x001f0000 /* free buffer pool number */ +#define uPD98401_RXVC_POOL_SHIFT 16 +#define uPD98401_RXVC_UINFO 0x0000ffff /* user-supplied information */ +#define uPD98401_RXVC_T1 0xffff0000 /* T1 timestamp */ +#define uPD98401_RXVC_T1_SHIFT 16 +#define uPD98401_RXVC_PR 0x00008000 /* Packet Reception, 1 if busy */ +#define uPD98401_RXVC_DR 0x00004000 /* FIFO Drop */ +#define uPD98401_RXVC_OD 0x00001000 /* Drop OAM cells */ +#define uPD98401_RXVC_AR 0x00000800 /* AAL5 or raw cell; 1 if AAL5 */ +#define uPD98401_RXVC_MAXSEG 0x000007ff /* max number of segments per PDU */ +#define uPD98401_RXVC_REM 0xfffe0000 /* remaining words in curr buffer */ +#define uPD98401_RXVC_REM_SHIFT 17 +#define uPD98401_RXVC_CLP 0x00010000 /* CLP received */ +#define uPD98401_RXVC_BFA 0x00008000 /* Buffer Assigned */ +#define uPD98401_RXVC_BTA 0x00004000 /* Batch Assigned */ +#define uPD98401_RXVC_CI 0x00002000 /* Congestion Indication */ +#define uPD98401_RXVC_DD 0x00001000 /* Dropping incoming cells */ +#define uPD98401_RXVC_DP 0x00000800 /* like PR ? */ +#define uPD98401_RXVC_CURSEG 0x000007ff /* Current Segment count */ + +/* + * RX lookup table + */ + +#define uPD98401_RXLT_ENBL 0x8000 /* Enable */ + +#endif diff --git a/drivers/atm/uPD98402.c b/drivers/atm/uPD98402.c new file mode 100644 index 000000000..239852d85 --- /dev/null +++ b/drivers/atm/uPD98402.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/atm/uPD98402.c - NEC uPD98402 (PHY) declarations */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uPD98402.h" + + +#if 0 +#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) +#else +#define DPRINTK(format,args...) +#endif + + +struct uPD98402_priv { + struct k_sonet_stats sonet_stats;/* link diagnostics */ + unsigned char framing; /* SONET/SDH framing */ + int loop_mode; /* loopback mode */ + spinlock_t lock; +}; + + +#define PRIV(dev) ((struct uPD98402_priv *) dev->phy_data) + +#define PUT(val,reg) dev->ops->phy_put(dev,val,uPD98402_##reg) +#define GET(reg) dev->ops->phy_get(dev,uPD98402_##reg) + + +static int fetch_stats(struct atm_dev *dev,struct sonet_stats __user *arg,int zero) +{ + struct sonet_stats tmp; + int error = 0; + + atomic_add(GET(HECCT),&PRIV(dev)->sonet_stats.uncorr_hcs); + sonet_copy_stats(&PRIV(dev)->sonet_stats,&tmp); + if (arg) error = copy_to_user(arg,&tmp,sizeof(tmp)); + if (zero && !error) { + /* unused fields are reported as -1, but we must not "adjust" + them */ + tmp.corr_hcs = tmp.tx_cells = tmp.rx_cells = 0; + sonet_subtract_stats(&PRIV(dev)->sonet_stats,&tmp); + } + return error ? -EFAULT : 0; +} + + +static int set_framing(struct atm_dev *dev,unsigned char framing) +{ + static const unsigned char sonet[] = { 1,2,3,0 }; + static const unsigned char sdh[] = { 1,0,0,2 }; + const char *set; + unsigned long flags; + + switch (framing) { + case SONET_FRAME_SONET: + set = sonet; + break; + case SONET_FRAME_SDH: + set = sdh; + break; + default: + return -EINVAL; + } + spin_lock_irqsave(&PRIV(dev)->lock, flags); + PUT(set[0],C11T); + PUT(set[1],C12T); + PUT(set[2],C13T); + PUT((GET(MDR) & ~uPD98402_MDR_SS_MASK) | (set[3] << + uPD98402_MDR_SS_SHIFT),MDR); + spin_unlock_irqrestore(&PRIV(dev)->lock, flags); + return 0; +} + + +static int get_sense(struct atm_dev *dev,u8 __user *arg) +{ + unsigned long flags; + unsigned char s[3]; + + spin_lock_irqsave(&PRIV(dev)->lock, flags); + s[0] = GET(C11R); + s[1] = GET(C12R); + s[2] = GET(C13R); + spin_unlock_irqrestore(&PRIV(dev)->lock, flags); + return (put_user(s[0], arg) || put_user(s[1], arg+1) || + put_user(s[2], arg+2) || put_user(0xff, arg+3) || + put_user(0xff, arg+4) || put_user(0xff, arg+5)) ? -EFAULT : 0; +} + + +static int set_loopback(struct atm_dev *dev,int mode) +{ + unsigned char mode_reg; + + mode_reg = GET(MDR) & ~(uPD98402_MDR_TPLP | uPD98402_MDR_ALP | + uPD98402_MDR_RPLP); + switch (__ATM_LM_XTLOC(mode)) { + case __ATM_LM_NONE: + break; + case __ATM_LM_PHY: + mode_reg |= uPD98402_MDR_TPLP; + break; + case __ATM_LM_ATM: + mode_reg |= uPD98402_MDR_ALP; + break; + default: + return -EINVAL; + } + switch (__ATM_LM_XTRMT(mode)) { + case __ATM_LM_NONE: + break; + case __ATM_LM_PHY: + mode_reg |= uPD98402_MDR_RPLP; + break; + default: + return -EINVAL; + } + PUT(mode_reg,MDR); + PRIV(dev)->loop_mode = mode; + return 0; +} + + +static int uPD98402_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + switch (cmd) { + + case SONET_GETSTATZ: + case SONET_GETSTAT: + return fetch_stats(dev,arg, cmd == SONET_GETSTATZ); + case SONET_SETFRAMING: + return set_framing(dev, (int)(unsigned long)arg); + case SONET_GETFRAMING: + return put_user(PRIV(dev)->framing,(int __user *)arg) ? + -EFAULT : 0; + case SONET_GETFRSENSE: + return get_sense(dev,arg); + case ATM_SETLOOP: + return set_loopback(dev, (int)(unsigned long)arg); + case ATM_GETLOOP: + return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ? + -EFAULT : 0; + case ATM_QUERYLOOP: + return put_user(ATM_LM_LOC_PHY | ATM_LM_LOC_ATM | + ATM_LM_RMT_PHY,(int __user *)arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + + +#define ADD_LIMITED(s,v) \ + { atomic_add(GET(v),&PRIV(dev)->sonet_stats.s); \ + if (atomic_read(&PRIV(dev)->sonet_stats.s) < 0) \ + atomic_set(&PRIV(dev)->sonet_stats.s,INT_MAX); } + + +static void stat_event(struct atm_dev *dev) +{ + unsigned char events; + + events = GET(PCR); + if (events & uPD98402_PFM_PFEB) ADD_LIMITED(path_febe,PFECB); + if (events & uPD98402_PFM_LFEB) ADD_LIMITED(line_febe,LECCT); + if (events & uPD98402_PFM_B3E) ADD_LIMITED(path_bip,B3ECT); + if (events & uPD98402_PFM_B2E) ADD_LIMITED(line_bip,B2ECT); + if (events & uPD98402_PFM_B1E) ADD_LIMITED(section_bip,B1ECT); +} + + +#undef ADD_LIMITED + + +static void uPD98402_int(struct atm_dev *dev) +{ + static unsigned long silence = 0; + unsigned char reason; + + while ((reason = GET(PICR))) { + if (reason & uPD98402_INT_LOS) + printk(KERN_NOTICE "%s(itf %d): signal lost\n", + dev->type,dev->number); + if (reason & uPD98402_INT_PFM) stat_event(dev); + if (reason & uPD98402_INT_PCO) { + (void) GET(PCOCR); /* clear interrupt cause */ + atomic_add(GET(HECCT), + &PRIV(dev)->sonet_stats.uncorr_hcs); + } + if ((reason & uPD98402_INT_RFO) && + (time_after(jiffies, silence) || silence == 0)) { + printk(KERN_WARNING "%s(itf %d): uPD98402 receive " + "FIFO overflow\n",dev->type,dev->number); + silence = (jiffies+HZ/2)|1; + } + } +} + + +static int uPD98402_start(struct atm_dev *dev) +{ + DPRINTK("phy_start\n"); + if (!(dev->phy_data = kmalloc(sizeof(struct uPD98402_priv),GFP_KERNEL))) + return -ENOMEM; + spin_lock_init(&PRIV(dev)->lock); + memset(&PRIV(dev)->sonet_stats,0,sizeof(struct k_sonet_stats)); + (void) GET(PCR); /* clear performance events */ + PUT(uPD98402_PFM_FJ,PCMR); /* ignore frequency adj */ + (void) GET(PCOCR); /* clear overflows */ + PUT(~uPD98402_PCO_HECC,PCOMR); + (void) GET(PICR); /* clear interrupts */ + PUT(~(uPD98402_INT_PFM | uPD98402_INT_ALM | uPD98402_INT_RFO | + uPD98402_INT_LOS),PIMR); /* enable them */ + (void) fetch_stats(dev,NULL,1); /* clear kernel counters */ + atomic_set(&PRIV(dev)->sonet_stats.corr_hcs,-1); + atomic_set(&PRIV(dev)->sonet_stats.tx_cells,-1); + atomic_set(&PRIV(dev)->sonet_stats.rx_cells,-1); + return 0; +} + + +static int uPD98402_stop(struct atm_dev *dev) +{ + /* let SAR driver worry about stopping interrupts */ + kfree(PRIV(dev)); + return 0; +} + + +static const struct atmphy_ops uPD98402_ops = { + .start = uPD98402_start, + .ioctl = uPD98402_ioctl, + .interrupt = uPD98402_int, + .stop = uPD98402_stop, +}; + + +int uPD98402_init(struct atm_dev *dev) +{ +DPRINTK("phy_init\n"); + dev->phy = &uPD98402_ops; + return 0; +} + + +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(uPD98402_init); + +static __init int uPD98402_module_init(void) +{ + return 0; +} +module_init(uPD98402_module_init); +/* module_exit not defined so not unloadable */ diff --git a/drivers/atm/uPD98402.h b/drivers/atm/uPD98402.h new file mode 100644 index 000000000..437cfaa20 --- /dev/null +++ b/drivers/atm/uPD98402.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/uPD98402.h - NEC uPD98402 (PHY) declarations */ + +/* Written 1995 by Werner Almesberger, EPFL LRC */ + + +#ifndef DRIVERS_ATM_uPD98402_H +#define DRIVERS_ATM_uPD98402_H + +/* + * Registers + */ + +#define uPD98402_CMR 0x00 /* Command Register */ +#define uPD98402_MDR 0x01 /* Mode Register */ +#define uPD98402_PICR 0x02 /* PHY Interrupt Cause Register */ +#define uPD98402_PIMR 0x03 /* PHY Interrupt Mask Register */ +#define uPD98402_ACR 0x04 /* Alarm Cause Register */ +#define uPD98402_ACMR 0x05 /* Alarm Cause Mask Register */ +#define uPD98402_PCR 0x06 /* Performance Cause Register */ +#define uPD98402_PCMR 0x07 /* Performance Cause Mask Register */ +#define uPD98402_IACM 0x08 /* Internal Alarm Cause Mask Register */ +#define uPD98402_B1ECT 0x09 /* B1 Error Count Register */ +#define uPD98402_B2ECT 0x0a /* B2 Error Count Register */ +#define uPD98402_B3ECT 0x0b /* B3 Error Count Regster */ +#define uPD98402_PFECB 0x0c /* Path FEBE Count Register */ +#define uPD98402_LECCT 0x0d /* Line FEBE Count Register */ +#define uPD98402_HECCT 0x0e /* HEC Error Count Register */ +#define uPD98402_FJCT 0x0f /* Frequence Justification Count Reg */ +#define uPD98402_PCOCR 0x10 /* Perf. Counter Overflow Cause Reg */ +#define uPD98402_PCOMR 0x11 /* Perf. Counter Overflow Mask Reg */ +#define uPD98402_C11T 0x20 /* C11T Data Register */ +#define uPD98402_C12T 0x21 /* C12T Data Register */ +#define uPD98402_C13T 0x22 /* C13T Data Register */ +#define uPD98402_F1T 0x23 /* F1T Data Register */ +#define uPD98402_K2T 0x25 /* K2T Data Register */ +#define uPD98402_C2T 0x26 /* C2T Data Register */ +#define uPD98402_F2T 0x27 /* F2T Data Register */ +#define uPD98402_C11R 0x30 /* C11T Data Register */ +#define uPD98402_C12R 0x31 /* C12T Data Register */ +#define uPD98402_C13R 0x32 /* C13T Data Register */ +#define uPD98402_F1R 0x33 /* F1T Data Register */ +#define uPD98402_K2R 0x35 /* K2T Data Register */ +#define uPD98402_C2R 0x36 /* C2T Data Register */ +#define uPD98402_F2R 0x37 /* F2T Data Register */ + +/* CMR is at 0x00 */ +#define uPD98402_CMR_PFRF 0x01 /* Send path FERF */ +#define uPD98402_CMR_LFRF 0x02 /* Send line FERF */ +#define uPD98402_CMR_PAIS 0x04 /* Send path AIS */ +#define uPD98402_CMR_LAIS 0x08 /* Send line AIS */ + +/* MDR is at 0x01 */ +#define uPD98402_MDR_ALP 0x01 /* ATM layer loopback */ +#define uPD98402_MDR_TPLP 0x02 /* PMD loopback, to host */ +#define uPD98402_MDR_RPLP 0x04 /* PMD loopback, to network */ +#define uPD98402_MDR_SS0 0x08 /* SS0 */ +#define uPD98402_MDR_SS1 0x10 /* SS1 */ +#define uPD98402_MDR_SS_MASK 0x18 /* mask */ +#define uPD98402_MDR_SS_SHIFT 3 /* shift */ +#define uPD98402_MDR_HEC 0x20 /* disable HEC inbound processing */ +#define uPD98402_MDR_FSR 0x40 /* disable frame scrambler */ +#define uPD98402_MDR_CSR 0x80 /* disable cell scrambler */ + +/* PICR is at 0x02, PIMR is at 0x03 */ +#define uPD98402_INT_PFM 0x01 /* performance counter has changed */ +#define uPD98402_INT_ALM 0x02 /* line fault */ +#define uPD98402_INT_RFO 0x04 /* receive FIFO overflow */ +#define uPD98402_INT_PCO 0x08 /* performance counter overflow */ +#define uPD98402_INT_OTD 0x20 /* OTD has occurred */ +#define uPD98402_INT_LOS 0x40 /* Loss Of Signal */ +#define uPD98402_INT_LOF 0x80 /* Loss Of Frame */ + +/* ACR is as 0x04, ACMR is at 0x05 */ +#define uPD98402_ALM_PFRF 0x01 /* path FERF */ +#define uPD98402_ALM_LFRF 0x02 /* line FERF */ +#define uPD98402_ALM_PAIS 0x04 /* path AIS */ +#define uPD98402_ALM_LAIS 0x08 /* line AIS */ +#define uPD98402_ALM_LOD 0x10 /* loss of delineation */ +#define uPD98402_ALM_LOP 0x20 /* loss of pointer */ +#define uPD98402_ALM_OOF 0x40 /* out of frame */ + +/* PCR is at 0x06, PCMR is at 0x07 */ +#define uPD98402_PFM_PFEB 0x01 /* path FEBE */ +#define uPD98402_PFM_LFEB 0x02 /* line FEBE */ +#define uPD98402_PFM_B3E 0x04 /* B3 error */ +#define uPD98402_PFM_B2E 0x08 /* B2 error */ +#define uPD98402_PFM_B1E 0x10 /* B1 error */ +#define uPD98402_PFM_FJ 0x20 /* frequency justification */ + +/* IACM is at 0x08 */ +#define uPD98402_IACM_PFRF 0x01 /* don't generate path FERF */ +#define uPD98402_IACM_LFRF 0x02 /* don't generate line FERF */ + +/* PCOCR is at 0x010, PCOMR is at 0x11 */ +#define uPD98402_PCO_B1EC 0x01 /* B1ECT overflow */ +#define uPD98402_PCO_B2EC 0x02 /* B2ECT overflow */ +#define uPD98402_PCO_B3EC 0x04 /* B3ECT overflow */ +#define uPD98402_PCO_PFBC 0x08 /* PFEBC overflow */ +#define uPD98402_PCO_LFBC 0x10 /* LFEVC overflow */ +#define uPD98402_PCO_HECC 0x20 /* HECCT overflow */ +#define uPD98402_PCO_FJC 0x40 /* FJCT overflow */ + + +int uPD98402_init(struct atm_dev *dev); + +#endif diff --git a/drivers/atm/zatm.c b/drivers/atm/zatm.c new file mode 100644 index 000000000..cf5fffcf9 --- /dev/null +++ b/drivers/atm/zatm.c @@ -0,0 +1,1652 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/atm/zatm.c - ZeitNet ZN122x device driver */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uPD98401.h" +#include "uPD98402.h" +#include "zeprom.h" +#include "zatm.h" + + +/* + * TODO: + * + * Minor features + * - support 64 kB SDUs (will have to use multibuffer batches then :-( ) + * - proper use of CDV, credit = max(1,CDVT*PCR) + * - AAL0 + * - better receive timestamps + * - OAM + */ + +#define ZATM_COPPER 1 + +#if 0 +#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) +#else +#define DPRINTK(format,args...) +#endif + +#ifndef CONFIG_ATM_ZATM_DEBUG + + +#define NULLCHECK(x) + +#define EVENT(s,a,b) + + +static void event_dump(void) +{ +} + + +#else + + +/* + * NULL pointer checking + */ + +#define NULLCHECK(x) \ + if ((unsigned long) (x) < 0x30) printk(KERN_CRIT #x "==0x%x\n", (int) (x)) + +/* + * Very extensive activity logging. Greatly improves bug detection speed but + * costs a few Mbps if enabled. + */ + +#define EV 64 + +static const char *ev[EV]; +static unsigned long ev_a[EV],ev_b[EV]; +static int ec = 0; + + +static void EVENT(const char *s,unsigned long a,unsigned long b) +{ + ev[ec] = s; + ev_a[ec] = a; + ev_b[ec] = b; + ec = (ec+1) % EV; +} + + +static void event_dump(void) +{ + int n,i; + + printk(KERN_NOTICE "----- event dump follows -----\n"); + for (n = 0; n < EV; n++) { + i = (ec+n) % EV; + printk(KERN_NOTICE); + printk(ev[i] ? ev[i] : "(null)",ev_a[i],ev_b[i]); + } + printk(KERN_NOTICE "----- event dump ends here -----\n"); +} + + +#endif /* CONFIG_ATM_ZATM_DEBUG */ + + +#define RING_BUSY 1 /* indication from do_tx that PDU has to be + backlogged */ + +static struct atm_dev *zatm_boards = NULL; +static unsigned long dummy[2] = {0,0}; + + +#define zin_n(r) inl(zatm_dev->base+r*4) +#define zin(r) inl(zatm_dev->base+uPD98401_##r*4) +#define zout(v,r) outl(v,zatm_dev->base+uPD98401_##r*4) +#define zwait() do {} while (zin(CMR) & uPD98401_BUSY) + +/* RX0, RX1, TX0, TX1 */ +static const int mbx_entries[NR_MBX] = { 1024,1024,1024,1024 }; +static const int mbx_esize[NR_MBX] = { 16,16,4,4 }; /* entry size in bytes */ + +#define MBX_SIZE(i) (mbx_entries[i]*mbx_esize[i]) + + +/*-------------------------------- utilities --------------------------------*/ + + +static void zpokel(struct zatm_dev *zatm_dev,u32 value,u32 addr) +{ + zwait(); + zout(value,CER); + zout(uPD98401_IND_ACC | uPD98401_IA_BALL | + (uPD98401_IA_TGT_CM << uPD98401_IA_TGT_SHIFT) | addr,CMR); +} + + +static u32 zpeekl(struct zatm_dev *zatm_dev,u32 addr) +{ + zwait(); + zout(uPD98401_IND_ACC | uPD98401_IA_BALL | uPD98401_IA_RW | + (uPD98401_IA_TGT_CM << uPD98401_IA_TGT_SHIFT) | addr,CMR); + zwait(); + return zin(CER); +} + + +/*------------------------------- free lists --------------------------------*/ + + +/* + * Free buffer head structure: + * [0] pointer to buffer (for SAR) + * [1] buffer descr link pointer (for SAR) + * [2] back pointer to skb (for poll_rx) + * [3] data + * ... + */ + +struct rx_buffer_head { + u32 buffer; /* pointer to buffer (for SAR) */ + u32 link; /* buffer descriptor link pointer (for SAR) */ + struct sk_buff *skb; /* back pointer to skb (for poll_rx) */ +}; + + +static void refill_pool(struct atm_dev *dev,int pool) +{ + struct zatm_dev *zatm_dev; + struct sk_buff *skb; + struct rx_buffer_head *first; + unsigned long flags; + int align,offset,free,count,size; + + EVENT("refill_pool\n",0,0); + zatm_dev = ZATM_DEV(dev); + size = (64 << (pool <= ZATM_AAL5_POOL_BASE ? 0 : + pool-ZATM_AAL5_POOL_BASE))+sizeof(struct rx_buffer_head); + if (size < PAGE_SIZE) { + align = 32; /* for 32 byte alignment */ + offset = sizeof(struct rx_buffer_head); + } + else { + align = 4096; + offset = zatm_dev->pool_info[pool].offset+ + sizeof(struct rx_buffer_head); + } + size += align; + spin_lock_irqsave(&zatm_dev->lock, flags); + free = zpeekl(zatm_dev,zatm_dev->pool_base+2*pool) & + uPD98401_RXFP_REMAIN; + spin_unlock_irqrestore(&zatm_dev->lock, flags); + if (free >= zatm_dev->pool_info[pool].low_water) return; + EVENT("starting ... POOL: 0x%x, 0x%x\n", + zpeekl(zatm_dev,zatm_dev->pool_base+2*pool), + zpeekl(zatm_dev,zatm_dev->pool_base+2*pool+1)); + EVENT("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]); + count = 0; + first = NULL; + while (free < zatm_dev->pool_info[pool].high_water) { + struct rx_buffer_head *head; + + skb = alloc_skb(size,GFP_ATOMIC); + if (!skb) { + printk(KERN_WARNING DEV_LABEL "(Itf %d): got no new " + "skb (%d) with %d free\n",dev->number,size,free); + break; + } + skb_reserve(skb,(unsigned char *) ((((unsigned long) skb->data+ + align+offset-1) & ~(unsigned long) (align-1))-offset)- + skb->data); + head = (struct rx_buffer_head *) skb->data; + skb_reserve(skb,sizeof(struct rx_buffer_head)); + if (!first) first = head; + count++; + head->buffer = virt_to_bus(skb->data); + head->link = 0; + head->skb = skb; + EVENT("enq skb 0x%08lx/0x%08lx\n",(unsigned long) skb, + (unsigned long) head); + spin_lock_irqsave(&zatm_dev->lock, flags); + if (zatm_dev->last_free[pool]) + ((struct rx_buffer_head *) (zatm_dev->last_free[pool]-> + data))[-1].link = virt_to_bus(head); + zatm_dev->last_free[pool] = skb; + skb_queue_tail(&zatm_dev->pool[pool],skb); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + free++; + } + if (first) { + spin_lock_irqsave(&zatm_dev->lock, flags); + zwait(); + zout(virt_to_bus(first),CER); + zout(uPD98401_ADD_BAT | (pool << uPD98401_POOL_SHIFT) | count, + CMR); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + EVENT ("POOL: 0x%x, 0x%x\n", + zpeekl(zatm_dev,zatm_dev->pool_base+2*pool), + zpeekl(zatm_dev,zatm_dev->pool_base+2*pool+1)); + EVENT("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]); + } +} + + +static void drain_free(struct atm_dev *dev,int pool) +{ + skb_queue_purge(&ZATM_DEV(dev)->pool[pool]); +} + + +static int pool_index(int max_pdu) +{ + int i; + + if (max_pdu % ATM_CELL_PAYLOAD) + printk(KERN_ERR DEV_LABEL ": driver error in pool_index: " + "max_pdu is %d\n",max_pdu); + if (max_pdu > 65536) return -1; + for (i = 0; (64 << i) < max_pdu; i++); + return i+ZATM_AAL5_POOL_BASE; +} + + +/* use_pool isn't reentrant */ + + +static void use_pool(struct atm_dev *dev,int pool) +{ + struct zatm_dev *zatm_dev; + unsigned long flags; + int size; + + zatm_dev = ZATM_DEV(dev); + if (!(zatm_dev->pool_info[pool].ref_count++)) { + skb_queue_head_init(&zatm_dev->pool[pool]); + size = pool-ZATM_AAL5_POOL_BASE; + if (size < 0) size = 0; /* 64B... */ + else if (size > 10) size = 10; /* ... 64kB */ + spin_lock_irqsave(&zatm_dev->lock, flags); + zpokel(zatm_dev,((zatm_dev->pool_info[pool].low_water/4) << + uPD98401_RXFP_ALERT_SHIFT) | + (1 << uPD98401_RXFP_BTSZ_SHIFT) | + (size << uPD98401_RXFP_BFSZ_SHIFT), + zatm_dev->pool_base+pool*2); + zpokel(zatm_dev,(unsigned long) dummy,zatm_dev->pool_base+ + pool*2+1); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + zatm_dev->last_free[pool] = NULL; + refill_pool(dev,pool); + } + DPRINTK("pool %d: %d\n",pool,zatm_dev->pool_info[pool].ref_count); +} + + +static void unuse_pool(struct atm_dev *dev,int pool) +{ + if (!(--ZATM_DEV(dev)->pool_info[pool].ref_count)) + drain_free(dev,pool); +} + +/*----------------------------------- RX ------------------------------------*/ + + +#if 0 +static void exception(struct atm_vcc *vcc) +{ + static int count = 0; + struct zatm_dev *zatm_dev = ZATM_DEV(vcc->dev); + struct zatm_vcc *zatm_vcc = ZATM_VCC(vcc); + unsigned long *qrp; + int i; + + if (count++ > 2) return; + for (i = 0; i < 8; i++) + printk("TX%d: 0x%08lx\n",i, + zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+i)); + for (i = 0; i < 5; i++) + printk("SH%d: 0x%08lx\n",i, + zpeekl(zatm_dev,uPD98401_IM(zatm_vcc->shaper)+16*i)); + qrp = (unsigned long *) zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+ + uPD98401_TXVC_QRP); + printk("qrp=0x%08lx\n",(unsigned long) qrp); + for (i = 0; i < 4; i++) printk("QRP[%d]: 0x%08lx",i,qrp[i]); +} +#endif + + +static const char *err_txt[] = { + "No error", + "RX buf underflow", + "RX FIFO overrun", + "Maximum len violation", + "CRC error", + "User abort", + "Length violation", + "T1 error", + "Deactivated", + "???", + "???", + "???", + "???", + "???", + "???", + "???" +}; + + +static void poll_rx(struct atm_dev *dev,int mbx) +{ + struct zatm_dev *zatm_dev; + unsigned long pos; + u32 x; + int error; + + EVENT("poll_rx\n",0,0); + zatm_dev = ZATM_DEV(dev); + pos = (zatm_dev->mbx_start[mbx] & ~0xffffUL) | zin(MTA(mbx)); + while (x = zin(MWA(mbx)), (pos & 0xffff) != x) { + u32 *here; + struct sk_buff *skb; + struct atm_vcc *vcc; + int cells,size,chan; + + EVENT("MBX: host 0x%lx, nic 0x%x\n",pos,x); + here = (u32 *) pos; + if (((pos += 16) & 0xffff) == zatm_dev->mbx_end[mbx]) + pos = zatm_dev->mbx_start[mbx]; + cells = here[0] & uPD98401_AAL5_SIZE; +#if 0 +printk("RX IND: 0x%x, 0x%x, 0x%x, 0x%x\n",here[0],here[1],here[2],here[3]); +{ +unsigned long *x; + printk("POOL: 0x%08x, 0x%08x\n",zpeekl(zatm_dev, + zatm_dev->pool_base), + zpeekl(zatm_dev,zatm_dev->pool_base+1)); + x = (unsigned long *) here[2]; + printk("[0..3] = 0x%08lx, 0x%08lx, 0x%08lx, 0x%08lx\n", + x[0],x[1],x[2],x[3]); +} +#endif + error = 0; + if (here[3] & uPD98401_AAL5_ERR) { + error = (here[3] & uPD98401_AAL5_ES) >> + uPD98401_AAL5_ES_SHIFT; + if (error == uPD98401_AAL5_ES_DEACT || + error == uPD98401_AAL5_ES_FREE) continue; + } +EVENT("error code 0x%x/0x%x\n",(here[3] & uPD98401_AAL5_ES) >> + uPD98401_AAL5_ES_SHIFT,error); + skb = ((struct rx_buffer_head *) bus_to_virt(here[2]))->skb; + __net_timestamp(skb); +#if 0 +printk("[-3..0] 0x%08lx 0x%08lx 0x%08lx 0x%08lx\n",((unsigned *) skb->data)[-3], + ((unsigned *) skb->data)[-2],((unsigned *) skb->data)[-1], + ((unsigned *) skb->data)[0]); +#endif + EVENT("skb 0x%lx, here 0x%lx\n",(unsigned long) skb, + (unsigned long) here); +#if 0 +printk("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]); +#endif + size = error ? 0 : ntohs(((__be16 *) skb->data)[cells* + ATM_CELL_PAYLOAD/sizeof(u16)-3]); + EVENT("got skb 0x%lx, size %d\n",(unsigned long) skb,size); + chan = (here[3] & uPD98401_AAL5_CHAN) >> + uPD98401_AAL5_CHAN_SHIFT; + if (chan < zatm_dev->chans && zatm_dev->rx_map[chan]) { + int pos; + vcc = zatm_dev->rx_map[chan]; + pos = ZATM_VCC(vcc)->pool; + if (skb == zatm_dev->last_free[pos]) + zatm_dev->last_free[pos] = NULL; + skb_unlink(skb, zatm_dev->pool + pos); + } + else { + printk(KERN_ERR DEV_LABEL "(itf %d): RX indication " + "for non-existing channel\n",dev->number); + size = 0; + vcc = NULL; + event_dump(); + } + if (error) { + static unsigned long silence = 0; + static int last_error = 0; + + if (error != last_error || + time_after(jiffies, silence) || silence == 0){ + printk(KERN_WARNING DEV_LABEL "(itf %d): " + "chan %d error %s\n",dev->number,chan, + err_txt[error]); + last_error = error; + silence = (jiffies+2*HZ)|1; + } + size = 0; + } + if (size && (size > cells*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER || + size <= (cells-1)*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER)) { + printk(KERN_ERR DEV_LABEL "(itf %d): size %d with %d " + "cells\n",dev->number,size,cells); + size = 0; + event_dump(); + } + if (size > ATM_MAX_AAL5_PDU) { + printk(KERN_ERR DEV_LABEL "(itf %d): size too big " + "(%d)\n",dev->number,size); + size = 0; + event_dump(); + } + if (!size) { + dev_kfree_skb_irq(skb); + if (vcc) atomic_inc(&vcc->stats->rx_err); + continue; + } + if (!atm_charge(vcc,skb->truesize)) { + dev_kfree_skb_irq(skb); + continue; + } + skb->len = size; + ATM_SKB(skb)->vcc = vcc; + vcc->push(vcc,skb); + atomic_inc(&vcc->stats->rx); + } + zout(pos & 0xffff,MTA(mbx)); +#if 0 /* probably a stupid idea */ + refill_pool(dev,zatm_vcc->pool); + /* maybe this saves us a few interrupts */ +#endif +} + + +static int open_rx_first(struct atm_vcc *vcc) +{ + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + unsigned long flags; + unsigned short chan; + int cells; + + DPRINTK("open_rx_first (0x%x)\n",inb_p(0xc053)); + zatm_dev = ZATM_DEV(vcc->dev); + zatm_vcc = ZATM_VCC(vcc); + zatm_vcc->rx_chan = 0; + if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0; + if (vcc->qos.aal == ATM_AAL5) { + if (vcc->qos.rxtp.max_sdu > 65464) + vcc->qos.rxtp.max_sdu = 65464; + /* fix this - we may want to receive 64kB SDUs + later */ + cells = DIV_ROUND_UP(vcc->qos.rxtp.max_sdu + ATM_AAL5_TRAILER, + ATM_CELL_PAYLOAD); + zatm_vcc->pool = pool_index(cells*ATM_CELL_PAYLOAD); + } + else { + cells = 1; + zatm_vcc->pool = ZATM_AAL0_POOL; + } + if (zatm_vcc->pool < 0) return -EMSGSIZE; + spin_lock_irqsave(&zatm_dev->lock, flags); + zwait(); + zout(uPD98401_OPEN_CHAN,CMR); + zwait(); + DPRINTK("0x%x 0x%x\n",zin(CMR),zin(CER)); + chan = (zin(CMR) & uPD98401_CHAN_ADDR) >> uPD98401_CHAN_ADDR_SHIFT; + spin_unlock_irqrestore(&zatm_dev->lock, flags); + DPRINTK("chan is %d\n",chan); + if (!chan) return -EAGAIN; + use_pool(vcc->dev,zatm_vcc->pool); + DPRINTK("pool %d\n",zatm_vcc->pool); + /* set up VC descriptor */ + spin_lock_irqsave(&zatm_dev->lock, flags); + zpokel(zatm_dev,zatm_vcc->pool << uPD98401_RXVC_POOL_SHIFT, + chan*VC_SIZE/4); + zpokel(zatm_dev,uPD98401_RXVC_OD | (vcc->qos.aal == ATM_AAL5 ? + uPD98401_RXVC_AR : 0) | cells,chan*VC_SIZE/4+1); + zpokel(zatm_dev,0,chan*VC_SIZE/4+2); + zatm_vcc->rx_chan = chan; + zatm_dev->rx_map[chan] = vcc; + spin_unlock_irqrestore(&zatm_dev->lock, flags); + return 0; +} + + +static int open_rx_second(struct atm_vcc *vcc) +{ + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + unsigned long flags; + int pos,shift; + + DPRINTK("open_rx_second (0x%x)\n",inb_p(0xc053)); + zatm_dev = ZATM_DEV(vcc->dev); + zatm_vcc = ZATM_VCC(vcc); + if (!zatm_vcc->rx_chan) return 0; + spin_lock_irqsave(&zatm_dev->lock, flags); + /* should also handle VPI @@@ */ + pos = vcc->vci >> 1; + shift = (1-(vcc->vci & 1)) << 4; + zpokel(zatm_dev,(zpeekl(zatm_dev,pos) & ~(0xffff << shift)) | + ((zatm_vcc->rx_chan | uPD98401_RXLT_ENBL) << shift),pos); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + return 0; +} + + +static void close_rx(struct atm_vcc *vcc) +{ + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + unsigned long flags; + int pos,shift; + + zatm_vcc = ZATM_VCC(vcc); + zatm_dev = ZATM_DEV(vcc->dev); + if (!zatm_vcc->rx_chan) return; + DPRINTK("close_rx\n"); + /* disable receiver */ + if (vcc->vpi != ATM_VPI_UNSPEC && vcc->vci != ATM_VCI_UNSPEC) { + spin_lock_irqsave(&zatm_dev->lock, flags); + pos = vcc->vci >> 1; + shift = (1-(vcc->vci & 1)) << 4; + zpokel(zatm_dev,zpeekl(zatm_dev,pos) & ~(0xffff << shift),pos); + zwait(); + zout(uPD98401_NOP,CMR); + zwait(); + zout(uPD98401_NOP,CMR); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + } + spin_lock_irqsave(&zatm_dev->lock, flags); + zwait(); + zout(uPD98401_DEACT_CHAN | uPD98401_CHAN_RT | (zatm_vcc->rx_chan << + uPD98401_CHAN_ADDR_SHIFT),CMR); + zwait(); + udelay(10); /* why oh why ... ? */ + zout(uPD98401_CLOSE_CHAN | uPD98401_CHAN_RT | (zatm_vcc->rx_chan << + uPD98401_CHAN_ADDR_SHIFT),CMR); + zwait(); + if (!(zin(CMR) & uPD98401_CHAN_ADDR)) + printk(KERN_CRIT DEV_LABEL "(itf %d): can't close RX channel " + "%d\n",vcc->dev->number,zatm_vcc->rx_chan); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + zatm_dev->rx_map[zatm_vcc->rx_chan] = NULL; + zatm_vcc->rx_chan = 0; + unuse_pool(vcc->dev,zatm_vcc->pool); +} + + +static int start_rx(struct atm_dev *dev) +{ + struct zatm_dev *zatm_dev; + int i; + + DPRINTK("start_rx\n"); + zatm_dev = ZATM_DEV(dev); + zatm_dev->rx_map = kcalloc(zatm_dev->chans, + sizeof(*zatm_dev->rx_map), + GFP_KERNEL); + if (!zatm_dev->rx_map) return -ENOMEM; + /* set VPI/VCI split (use all VCIs and give what's left to VPIs) */ + zpokel(zatm_dev,(1 << dev->ci_range.vci_bits)-1,uPD98401_VRR); + /* prepare free buffer pools */ + for (i = 0; i <= ZATM_LAST_POOL; i++) { + zatm_dev->pool_info[i].ref_count = 0; + zatm_dev->pool_info[i].rqa_count = 0; + zatm_dev->pool_info[i].rqu_count = 0; + zatm_dev->pool_info[i].low_water = LOW_MARK; + zatm_dev->pool_info[i].high_water = HIGH_MARK; + zatm_dev->pool_info[i].offset = 0; + zatm_dev->pool_info[i].next_off = 0; + zatm_dev->pool_info[i].next_cnt = 0; + zatm_dev->pool_info[i].next_thres = OFF_CNG_THRES; + } + return 0; +} + + +/*----------------------------------- TX ------------------------------------*/ + + +static int do_tx(struct sk_buff *skb) +{ + struct atm_vcc *vcc; + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + u32 *dsc; + unsigned long flags; + + EVENT("do_tx\n",0,0); + DPRINTK("sending skb %p\n",skb); + vcc = ATM_SKB(skb)->vcc; + zatm_dev = ZATM_DEV(vcc->dev); + zatm_vcc = ZATM_VCC(vcc); + EVENT("iovcnt=%d\n",skb_shinfo(skb)->nr_frags,0); + spin_lock_irqsave(&zatm_dev->lock, flags); + if (!skb_shinfo(skb)->nr_frags) { + if (zatm_vcc->txing == RING_ENTRIES-1) { + spin_unlock_irqrestore(&zatm_dev->lock, flags); + return RING_BUSY; + } + zatm_vcc->txing++; + dsc = zatm_vcc->ring+zatm_vcc->ring_curr; + zatm_vcc->ring_curr = (zatm_vcc->ring_curr+RING_WORDS) & + (RING_ENTRIES*RING_WORDS-1); + dsc[1] = 0; + dsc[2] = skb->len; + dsc[3] = virt_to_bus(skb->data); + mb(); + dsc[0] = uPD98401_TXPD_V | uPD98401_TXPD_DP | uPD98401_TXPD_SM + | (vcc->qos.aal == ATM_AAL5 ? uPD98401_TXPD_AAL5 : 0 | + (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? + uPD98401_CLPM_1 : uPD98401_CLPM_0)); + EVENT("dsc (0x%lx)\n",(unsigned long) dsc,0); + } + else { +printk("NONONONOO!!!!\n"); + dsc = NULL; +#if 0 + u32 *put; + int i; + + dsc = kmalloc(uPD98401_TXPD_SIZE * 2 + + uPD98401_TXBD_SIZE * ATM_SKB(skb)->iovcnt, GFP_ATOMIC); + if (!dsc) { + if (vcc->pop) + vcc->pop(vcc, skb); + else + dev_kfree_skb_irq(skb); + return -EAGAIN; + } + /* @@@ should check alignment */ + put = dsc+8; + dsc[0] = uPD98401_TXPD_V | uPD98401_TXPD_DP | + (vcc->aal == ATM_AAL5 ? uPD98401_TXPD_AAL5 : 0 | + (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? + uPD98401_CLPM_1 : uPD98401_CLPM_0)); + dsc[1] = 0; + dsc[2] = ATM_SKB(skb)->iovcnt * uPD98401_TXBD_SIZE; + dsc[3] = virt_to_bus(put); + for (i = 0; i < ATM_SKB(skb)->iovcnt; i++) { + *put++ = ((struct iovec *) skb->data)[i].iov_len; + *put++ = virt_to_bus(((struct iovec *) + skb->data)[i].iov_base); + } + put[-2] |= uPD98401_TXBD_LAST; +#endif + } + ZATM_PRV_DSC(skb) = dsc; + skb_queue_tail(&zatm_vcc->tx_queue,skb); + DPRINTK("QRP=0x%08lx\n",zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+ + uPD98401_TXVC_QRP)); + zwait(); + zout(uPD98401_TX_READY | (zatm_vcc->tx_chan << + uPD98401_CHAN_ADDR_SHIFT),CMR); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + EVENT("done\n",0,0); + return 0; +} + + +static inline void dequeue_tx(struct atm_vcc *vcc) +{ + struct zatm_vcc *zatm_vcc; + struct sk_buff *skb; + + EVENT("dequeue_tx\n",0,0); + zatm_vcc = ZATM_VCC(vcc); + skb = skb_dequeue(&zatm_vcc->tx_queue); + if (!skb) { + printk(KERN_CRIT DEV_LABEL "(itf %d): dequeue_tx but not " + "txing\n",vcc->dev->number); + return; + } +#if 0 /* @@@ would fail on CLP */ +if (*ZATM_PRV_DSC(skb) != (uPD98401_TXPD_V | uPD98401_TXPD_DP | + uPD98401_TXPD_SM | uPD98401_TXPD_AAL5)) printk("@#*$!!!! (%08x)\n", + *ZATM_PRV_DSC(skb)); +#endif + *ZATM_PRV_DSC(skb) = 0; /* mark as invalid */ + zatm_vcc->txing--; + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb_irq(skb); + while ((skb = skb_dequeue(&zatm_vcc->backlog))) + if (do_tx(skb) == RING_BUSY) { + skb_queue_head(&zatm_vcc->backlog,skb); + break; + } + atomic_inc(&vcc->stats->tx); + wake_up(&zatm_vcc->tx_wait); +} + + +static void poll_tx(struct atm_dev *dev,int mbx) +{ + struct zatm_dev *zatm_dev; + unsigned long pos; + u32 x; + + EVENT("poll_tx\n",0,0); + zatm_dev = ZATM_DEV(dev); + pos = (zatm_dev->mbx_start[mbx] & ~0xffffUL) | zin(MTA(mbx)); + while (x = zin(MWA(mbx)), (pos & 0xffff) != x) { + int chan; + +#if 1 + u32 data,*addr; + + EVENT("MBX: host 0x%lx, nic 0x%x\n",pos,x); + addr = (u32 *) pos; + data = *addr; + chan = (data & uPD98401_TXI_CONN) >> uPD98401_TXI_CONN_SHIFT; + EVENT("addr = 0x%lx, data = 0x%08x,",(unsigned long) addr, + data); + EVENT("chan = %d\n",chan,0); +#else +NO ! + chan = (zatm_dev->mbx_start[mbx][pos >> 2] & uPD98401_TXI_CONN) + >> uPD98401_TXI_CONN_SHIFT; +#endif + if (chan < zatm_dev->chans && zatm_dev->tx_map[chan]) + dequeue_tx(zatm_dev->tx_map[chan]); + else { + printk(KERN_CRIT DEV_LABEL "(itf %d): TX indication " + "for non-existing channel %d\n",dev->number,chan); + event_dump(); + } + if (((pos += 4) & 0xffff) == zatm_dev->mbx_end[mbx]) + pos = zatm_dev->mbx_start[mbx]; + } + zout(pos & 0xffff,MTA(mbx)); +} + + +/* + * BUG BUG BUG: Doesn't handle "new-style" rate specification yet. + */ + +static int alloc_shaper(struct atm_dev *dev,int *pcr,int min,int max,int ubr) +{ + struct zatm_dev *zatm_dev; + unsigned long flags; + unsigned long i,m,c; + int shaper; + + DPRINTK("alloc_shaper (min = %d, max = %d)\n",min,max); + zatm_dev = ZATM_DEV(dev); + if (!zatm_dev->free_shapers) return -EAGAIN; + for (shaper = 0; !((zatm_dev->free_shapers >> shaper) & 1); shaper++); + zatm_dev->free_shapers &= ~1 << shaper; + if (ubr) { + c = 5; + i = m = 1; + zatm_dev->ubr_ref_cnt++; + zatm_dev->ubr = shaper; + *pcr = 0; + } + else { + if (min) { + if (min <= 255) { + i = min; + m = ATM_OC3_PCR; + } + else { + i = 255; + m = ATM_OC3_PCR*255/min; + } + } + else { + if (max > zatm_dev->tx_bw) max = zatm_dev->tx_bw; + if (max <= 255) { + i = max; + m = ATM_OC3_PCR; + } + else { + i = 255; + m = DIV_ROUND_UP(ATM_OC3_PCR*255, max); + } + } + if (i > m) { + printk(KERN_CRIT DEV_LABEL "shaper algorithm botched " + "[%d,%d] -> i=%ld,m=%ld\n",min,max,i,m); + m = i; + } + *pcr = i*ATM_OC3_PCR/m; + c = 20; /* @@@ should use max_cdv ! */ + if ((min && *pcr < min) || (max && *pcr > max)) return -EINVAL; + if (zatm_dev->tx_bw < *pcr) return -EAGAIN; + zatm_dev->tx_bw -= *pcr; + } + spin_lock_irqsave(&zatm_dev->lock, flags); + DPRINTK("i = %d, m = %d, PCR = %d\n",i,m,*pcr); + zpokel(zatm_dev,(i << uPD98401_IM_I_SHIFT) | m,uPD98401_IM(shaper)); + zpokel(zatm_dev,c << uPD98401_PC_C_SHIFT,uPD98401_PC(shaper)); + zpokel(zatm_dev,0,uPD98401_X(shaper)); + zpokel(zatm_dev,0,uPD98401_Y(shaper)); + zpokel(zatm_dev,uPD98401_PS_E,uPD98401_PS(shaper)); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + return shaper; +} + + +static void dealloc_shaper(struct atm_dev *dev,int shaper) +{ + struct zatm_dev *zatm_dev; + unsigned long flags; + + zatm_dev = ZATM_DEV(dev); + if (shaper == zatm_dev->ubr) { + if (--zatm_dev->ubr_ref_cnt) return; + zatm_dev->ubr = -1; + } + spin_lock_irqsave(&zatm_dev->lock, flags); + zpokel(zatm_dev,zpeekl(zatm_dev,uPD98401_PS(shaper)) & ~uPD98401_PS_E, + uPD98401_PS(shaper)); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + zatm_dev->free_shapers |= 1 << shaper; +} + + +static void close_tx(struct atm_vcc *vcc) +{ + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + unsigned long flags; + int chan; + + zatm_vcc = ZATM_VCC(vcc); + zatm_dev = ZATM_DEV(vcc->dev); + chan = zatm_vcc->tx_chan; + if (!chan) return; + DPRINTK("close_tx\n"); + if (skb_peek(&zatm_vcc->backlog)) { + printk("waiting for backlog to drain ...\n"); + event_dump(); + wait_event(zatm_vcc->tx_wait, !skb_peek(&zatm_vcc->backlog)); + } + if (skb_peek(&zatm_vcc->tx_queue)) { + printk("waiting for TX queue to drain ...\n"); + event_dump(); + wait_event(zatm_vcc->tx_wait, !skb_peek(&zatm_vcc->tx_queue)); + } + spin_lock_irqsave(&zatm_dev->lock, flags); +#if 0 + zwait(); + zout(uPD98401_DEACT_CHAN | (chan << uPD98401_CHAN_ADDR_SHIFT),CMR); +#endif + zwait(); + zout(uPD98401_CLOSE_CHAN | (chan << uPD98401_CHAN_ADDR_SHIFT),CMR); + zwait(); + if (!(zin(CMR) & uPD98401_CHAN_ADDR)) + printk(KERN_CRIT DEV_LABEL "(itf %d): can't close TX channel " + "%d\n",vcc->dev->number,chan); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + zatm_vcc->tx_chan = 0; + zatm_dev->tx_map[chan] = NULL; + if (zatm_vcc->shaper != zatm_dev->ubr) { + zatm_dev->tx_bw += vcc->qos.txtp.min_pcr; + dealloc_shaper(vcc->dev,zatm_vcc->shaper); + } + kfree(zatm_vcc->ring); +} + + +static int open_tx_first(struct atm_vcc *vcc) +{ + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + unsigned long flags; + u32 *loop; + unsigned short chan; + int unlimited; + + DPRINTK("open_tx_first\n"); + zatm_dev = ZATM_DEV(vcc->dev); + zatm_vcc = ZATM_VCC(vcc); + zatm_vcc->tx_chan = 0; + if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0; + spin_lock_irqsave(&zatm_dev->lock, flags); + zwait(); + zout(uPD98401_OPEN_CHAN,CMR); + zwait(); + DPRINTK("0x%x 0x%x\n",zin(CMR),zin(CER)); + chan = (zin(CMR) & uPD98401_CHAN_ADDR) >> uPD98401_CHAN_ADDR_SHIFT; + spin_unlock_irqrestore(&zatm_dev->lock, flags); + DPRINTK("chan is %d\n",chan); + if (!chan) return -EAGAIN; + unlimited = vcc->qos.txtp.traffic_class == ATM_UBR && + (!vcc->qos.txtp.max_pcr || vcc->qos.txtp.max_pcr == ATM_MAX_PCR || + vcc->qos.txtp.max_pcr >= ATM_OC3_PCR); + if (unlimited && zatm_dev->ubr != -1) zatm_vcc->shaper = zatm_dev->ubr; + else { + int pcr; + + if (unlimited) vcc->qos.txtp.max_sdu = ATM_MAX_AAL5_PDU; + if ((zatm_vcc->shaper = alloc_shaper(vcc->dev,&pcr, + vcc->qos.txtp.min_pcr,vcc->qos.txtp.max_pcr,unlimited)) + < 0) { + close_tx(vcc); + return zatm_vcc->shaper; + } + if (pcr > ATM_OC3_PCR) pcr = ATM_OC3_PCR; + vcc->qos.txtp.min_pcr = vcc->qos.txtp.max_pcr = pcr; + } + zatm_vcc->tx_chan = chan; + skb_queue_head_init(&zatm_vcc->tx_queue); + init_waitqueue_head(&zatm_vcc->tx_wait); + /* initialize ring */ + zatm_vcc->ring = kzalloc(RING_SIZE,GFP_KERNEL); + if (!zatm_vcc->ring) return -ENOMEM; + loop = zatm_vcc->ring+RING_ENTRIES*RING_WORDS; + loop[0] = uPD98401_TXPD_V; + loop[1] = loop[2] = 0; + loop[3] = virt_to_bus(zatm_vcc->ring); + zatm_vcc->ring_curr = 0; + zatm_vcc->txing = 0; + skb_queue_head_init(&zatm_vcc->backlog); + zpokel(zatm_dev,virt_to_bus(zatm_vcc->ring), + chan*VC_SIZE/4+uPD98401_TXVC_QRP); + return 0; +} + + +static int open_tx_second(struct atm_vcc *vcc) +{ + struct zatm_dev *zatm_dev; + struct zatm_vcc *zatm_vcc; + unsigned long flags; + + DPRINTK("open_tx_second\n"); + zatm_dev = ZATM_DEV(vcc->dev); + zatm_vcc = ZATM_VCC(vcc); + if (!zatm_vcc->tx_chan) return 0; + /* set up VC descriptor */ + spin_lock_irqsave(&zatm_dev->lock, flags); + zpokel(zatm_dev,0,zatm_vcc->tx_chan*VC_SIZE/4); + zpokel(zatm_dev,uPD98401_TXVC_L | (zatm_vcc->shaper << + uPD98401_TXVC_SHP_SHIFT) | (vcc->vpi << uPD98401_TXVC_VPI_SHIFT) | + vcc->vci,zatm_vcc->tx_chan*VC_SIZE/4+1); + zpokel(zatm_dev,0,zatm_vcc->tx_chan*VC_SIZE/4+2); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + zatm_dev->tx_map[zatm_vcc->tx_chan] = vcc; + return 0; +} + + +static int start_tx(struct atm_dev *dev) +{ + struct zatm_dev *zatm_dev; + int i; + + DPRINTK("start_tx\n"); + zatm_dev = ZATM_DEV(dev); + zatm_dev->tx_map = kmalloc_array(zatm_dev->chans, + sizeof(*zatm_dev->tx_map), + GFP_KERNEL); + if (!zatm_dev->tx_map) return -ENOMEM; + zatm_dev->tx_bw = ATM_OC3_PCR; + zatm_dev->free_shapers = (1 << NR_SHAPERS)-1; + zatm_dev->ubr = -1; + zatm_dev->ubr_ref_cnt = 0; + /* initialize shapers */ + for (i = 0; i < NR_SHAPERS; i++) zpokel(zatm_dev,0,uPD98401_PS(i)); + return 0; +} + + +/*------------------------------- interrupts --------------------------------*/ + + +static irqreturn_t zatm_int(int irq,void *dev_id) +{ + struct atm_dev *dev; + struct zatm_dev *zatm_dev; + u32 reason; + int handled = 0; + + dev = dev_id; + zatm_dev = ZATM_DEV(dev); + while ((reason = zin(GSR))) { + handled = 1; + EVENT("reason 0x%x\n",reason,0); + if (reason & uPD98401_INT_PI) { + EVENT("PHY int\n",0,0); + dev->phy->interrupt(dev); + } + if (reason & uPD98401_INT_RQA) { + unsigned long pools; + int i; + + pools = zin(RQA); + EVENT("RQA (0x%08x)\n",pools,0); + for (i = 0; pools; i++) { + if (pools & 1) { + refill_pool(dev,i); + zatm_dev->pool_info[i].rqa_count++; + } + pools >>= 1; + } + } + if (reason & uPD98401_INT_RQU) { + unsigned long pools; + int i; + pools = zin(RQU); + printk(KERN_WARNING DEV_LABEL "(itf %d): RQU 0x%08lx\n", + dev->number,pools); + event_dump(); + for (i = 0; pools; i++) { + if (pools & 1) { + refill_pool(dev,i); + zatm_dev->pool_info[i].rqu_count++; + } + pools >>= 1; + } + } + /* don't handle RD */ + if (reason & uPD98401_INT_SPE) + printk(KERN_ALERT DEV_LABEL "(itf %d): system parity " + "error at 0x%08x\n",dev->number,zin(ADDR)); + if (reason & uPD98401_INT_CPE) + printk(KERN_ALERT DEV_LABEL "(itf %d): control memory " + "parity error at 0x%08x\n",dev->number,zin(ADDR)); + if (reason & uPD98401_INT_SBE) { + printk(KERN_ALERT DEV_LABEL "(itf %d): system bus " + "error at 0x%08x\n",dev->number,zin(ADDR)); + event_dump(); + } + /* don't handle IND */ + if (reason & uPD98401_INT_MF) { + printk(KERN_CRIT DEV_LABEL "(itf %d): mailbox full " + "(0x%x)\n",dev->number,(reason & uPD98401_INT_MF) + >> uPD98401_INT_MF_SHIFT); + event_dump(); + /* @@@ should try to recover */ + } + if (reason & uPD98401_INT_MM) { + if (reason & 1) poll_rx(dev,0); + if (reason & 2) poll_rx(dev,1); + if (reason & 4) poll_tx(dev,2); + if (reason & 8) poll_tx(dev,3); + } + /* @@@ handle RCRn */ + } + return IRQ_RETVAL(handled); +} + + +/*----------------------------- (E)EPROM access -----------------------------*/ + + +static void eprom_set(struct zatm_dev *zatm_dev, unsigned long value, + unsigned short cmd) +{ + int error; + + if ((error = pci_write_config_dword(zatm_dev->pci_dev,cmd,value))) + printk(KERN_ERR DEV_LABEL ": PCI write failed (0x%02x)\n", + error); +} + + +static unsigned long eprom_get(struct zatm_dev *zatm_dev, unsigned short cmd) +{ + unsigned int value; + int error; + + if ((error = pci_read_config_dword(zatm_dev->pci_dev,cmd,&value))) + printk(KERN_ERR DEV_LABEL ": PCI read failed (0x%02x)\n", + error); + return value; +} + + +static void eprom_put_bits(struct zatm_dev *zatm_dev, unsigned long data, + int bits, unsigned short cmd) +{ + unsigned long value; + int i; + + for (i = bits-1; i >= 0; i--) { + value = ZEPROM_CS | (((data >> i) & 1) ? ZEPROM_DI : 0); + eprom_set(zatm_dev,value,cmd); + eprom_set(zatm_dev,value | ZEPROM_SK,cmd); + eprom_set(zatm_dev,value,cmd); + } +} + + +static void eprom_get_byte(struct zatm_dev *zatm_dev, unsigned char *byte, + unsigned short cmd) +{ + int i; + + *byte = 0; + for (i = 8; i; i--) { + eprom_set(zatm_dev,ZEPROM_CS,cmd); + eprom_set(zatm_dev,ZEPROM_CS | ZEPROM_SK,cmd); + *byte <<= 1; + if (eprom_get(zatm_dev,cmd) & ZEPROM_DO) *byte |= 1; + eprom_set(zatm_dev,ZEPROM_CS,cmd); + } +} + + +static int eprom_try_esi(struct atm_dev *dev, unsigned short cmd, int offset, + int swap) +{ + unsigned char buf[ZEPROM_SIZE]; + struct zatm_dev *zatm_dev; + int i; + + zatm_dev = ZATM_DEV(dev); + for (i = 0; i < ZEPROM_SIZE; i += 2) { + eprom_set(zatm_dev,ZEPROM_CS,cmd); /* select EPROM */ + eprom_put_bits(zatm_dev,ZEPROM_CMD_READ,ZEPROM_CMD_LEN,cmd); + eprom_put_bits(zatm_dev,i >> 1,ZEPROM_ADDR_LEN,cmd); + eprom_get_byte(zatm_dev,buf+i+swap,cmd); + eprom_get_byte(zatm_dev,buf+i+1-swap,cmd); + eprom_set(zatm_dev,0,cmd); /* deselect EPROM */ + } + memcpy(dev->esi,buf+offset,ESI_LEN); + return memcmp(dev->esi,"\0\0\0\0\0",ESI_LEN); /* assumes ESI_LEN == 6 */ +} + + +static void eprom_get_esi(struct atm_dev *dev) +{ + if (eprom_try_esi(dev,ZEPROM_V1_REG,ZEPROM_V1_ESI_OFF,1)) return; + (void) eprom_try_esi(dev,ZEPROM_V2_REG,ZEPROM_V2_ESI_OFF,0); +} + + +/*--------------------------------- entries ---------------------------------*/ + + +static int zatm_init(struct atm_dev *dev) +{ + struct zatm_dev *zatm_dev; + struct pci_dev *pci_dev; + unsigned short command; + int error,i,last; + unsigned long t0,t1,t2; + + DPRINTK(">zatm_init\n"); + zatm_dev = ZATM_DEV(dev); + spin_lock_init(&zatm_dev->lock); + pci_dev = zatm_dev->pci_dev; + zatm_dev->base = pci_resource_start(pci_dev, 0); + zatm_dev->irq = pci_dev->irq; + if ((error = pci_read_config_word(pci_dev,PCI_COMMAND,&command))) { + printk(KERN_ERR DEV_LABEL "(itf %d): init error 0x%02x\n", + dev->number,error); + return -EINVAL; + } + if ((error = pci_write_config_word(pci_dev,PCI_COMMAND, + command | PCI_COMMAND_IO | PCI_COMMAND_MASTER))) { + printk(KERN_ERR DEV_LABEL "(itf %d): can't enable IO (0x%02x)" + "\n",dev->number,error); + return -EIO; + } + eprom_get_esi(dev); + printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d,base=0x%x,irq=%d,", + dev->number,pci_dev->revision,zatm_dev->base,zatm_dev->irq); + /* reset uPD98401 */ + zout(0,SWR); + while (!(zin(GSR) & uPD98401_INT_IND)); + zout(uPD98401_GMR_ONE /*uPD98401_BURST4*/,GMR); + last = MAX_CRAM_SIZE; + for (i = last-RAM_INCREMENT; i >= 0; i -= RAM_INCREMENT) { + zpokel(zatm_dev,0x55555555,i); + if (zpeekl(zatm_dev,i) != 0x55555555) last = i; + else { + zpokel(zatm_dev,0xAAAAAAAA,i); + if (zpeekl(zatm_dev,i) != 0xAAAAAAAA) last = i; + else zpokel(zatm_dev,i,i); + } + } + for (i = 0; i < last; i += RAM_INCREMENT) + if (zpeekl(zatm_dev,i) != i) break; + zatm_dev->mem = i << 2; + while (i) zpokel(zatm_dev,0,--i); + /* reset again to rebuild memory pointers */ + zout(0,SWR); + while (!(zin(GSR) & uPD98401_INT_IND)); + zout(uPD98401_GMR_ONE | uPD98401_BURST8 | uPD98401_BURST4 | + uPD98401_BURST2 | uPD98401_GMR_PM | uPD98401_GMR_DR,GMR); + /* TODO: should shrink allocation now */ + printk("mem=%dkB,%s (",zatm_dev->mem >> 10,zatm_dev->copper ? "UTP" : + "MMF"); + for (i = 0; i < ESI_LEN; i++) + printk("%02X%s",dev->esi[i],i == ESI_LEN-1 ? ")\n" : "-"); + do { + unsigned long flags; + + spin_lock_irqsave(&zatm_dev->lock, flags); + t0 = zpeekl(zatm_dev,uPD98401_TSR); + udelay(10); + t1 = zpeekl(zatm_dev,uPD98401_TSR); + udelay(1010); + t2 = zpeekl(zatm_dev,uPD98401_TSR); + spin_unlock_irqrestore(&zatm_dev->lock, flags); + } + while (t0 > t1 || t1 > t2); /* loop if wrapping ... */ + zatm_dev->khz = t2-2*t1+t0; + printk(KERN_NOTICE DEV_LABEL "(itf %d): uPD98401 %d.%d at %d.%03d " + "MHz\n",dev->number, + (zin(VER) & uPD98401_MAJOR) >> uPD98401_MAJOR_SHIFT, + zin(VER) & uPD98401_MINOR,zatm_dev->khz/1000,zatm_dev->khz % 1000); + return uPD98402_init(dev); +} + + +static int zatm_start(struct atm_dev *dev) +{ + struct zatm_dev *zatm_dev = ZATM_DEV(dev); + struct pci_dev *pdev = zatm_dev->pci_dev; + unsigned long curr; + int pools,vccs,rx; + int error, i, ld; + + DPRINTK("zatm_start\n"); + zatm_dev->rx_map = zatm_dev->tx_map = NULL; + for (i = 0; i < NR_MBX; i++) + zatm_dev->mbx_start[i] = 0; + error = request_irq(zatm_dev->irq, zatm_int, IRQF_SHARED, DEV_LABEL, dev); + if (error < 0) { + printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n", + dev->number,zatm_dev->irq); + goto done; + } + /* define memory regions */ + pools = NR_POOLS; + if (NR_SHAPERS*SHAPER_SIZE > pools*POOL_SIZE) + pools = NR_SHAPERS*SHAPER_SIZE/POOL_SIZE; + vccs = (zatm_dev->mem-NR_SHAPERS*SHAPER_SIZE-pools*POOL_SIZE)/ + (2*VC_SIZE+RX_SIZE); + ld = -1; + for (rx = 1; rx < vccs; rx <<= 1) ld++; + dev->ci_range.vpi_bits = 0; /* @@@ no VPI for now */ + dev->ci_range.vci_bits = ld; + dev->link_rate = ATM_OC3_PCR; + zatm_dev->chans = vccs; /* ??? */ + curr = rx*RX_SIZE/4; + DPRINTK("RX pool 0x%08lx\n",curr); + zpokel(zatm_dev,curr,uPD98401_PMA); /* receive pool */ + zatm_dev->pool_base = curr; + curr += pools*POOL_SIZE/4; + DPRINTK("Shapers 0x%08lx\n",curr); + zpokel(zatm_dev,curr,uPD98401_SMA); /* shapers */ + curr += NR_SHAPERS*SHAPER_SIZE/4; + DPRINTK("Free 0x%08lx\n",curr); + zpokel(zatm_dev,curr,uPD98401_TOS); /* free pool */ + printk(KERN_INFO DEV_LABEL "(itf %d): %d shapers, %d pools, %d RX, " + "%ld VCs\n",dev->number,NR_SHAPERS,pools,rx, + (zatm_dev->mem-curr*4)/VC_SIZE); + /* create mailboxes */ + for (i = 0; i < NR_MBX; i++) { + void *mbx; + dma_addr_t mbx_dma; + + if (!mbx_entries[i]) + continue; + mbx = dma_alloc_coherent(&pdev->dev, + 2 * MBX_SIZE(i), &mbx_dma, GFP_KERNEL); + if (!mbx) { + error = -ENOMEM; + goto out; + } + /* + * Alignment provided by dma_alloc_coherent() isn't enough + * for this device. + */ + if (((unsigned long)mbx ^ mbx_dma) & 0xffff) { + printk(KERN_ERR DEV_LABEL "(itf %d): system " + "bus incompatible with driver\n", dev->number); + dma_free_coherent(&pdev->dev, 2*MBX_SIZE(i), mbx, mbx_dma); + error = -ENODEV; + goto out; + } + DPRINTK("mbx@0x%08lx-0x%08lx\n", mbx, mbx + MBX_SIZE(i)); + zatm_dev->mbx_start[i] = (unsigned long)mbx; + zatm_dev->mbx_dma[i] = mbx_dma; + zatm_dev->mbx_end[i] = (zatm_dev->mbx_start[i] + MBX_SIZE(i)) & + 0xffff; + zout(mbx_dma >> 16, MSH(i)); + zout(mbx_dma, MSL(i)); + zout(zatm_dev->mbx_end[i], MBA(i)); + zout((unsigned long)mbx & 0xffff, MTA(i)); + zout((unsigned long)mbx & 0xffff, MWA(i)); + } + error = start_tx(dev); + if (error) + goto out; + error = start_rx(dev); + if (error) + goto out_tx; + error = dev->phy->start(dev); + if (error) + goto out_rx; + zout(0xffffffff,IMR); /* enable interrupts */ + /* enable TX & RX */ + zout(zin(GMR) | uPD98401_GMR_SE | uPD98401_GMR_RE,GMR); +done: + return error; + +out_rx: + kfree(zatm_dev->rx_map); +out_tx: + kfree(zatm_dev->tx_map); +out: + while (i-- > 0) { + dma_free_coherent(&pdev->dev, 2 * MBX_SIZE(i), + (void *)zatm_dev->mbx_start[i], + zatm_dev->mbx_dma[i]); + } + free_irq(zatm_dev->irq, dev); + goto done; +} + + +static void zatm_close(struct atm_vcc *vcc) +{ + DPRINTK(">zatm_close\n"); + if (!ZATM_VCC(vcc)) return; + clear_bit(ATM_VF_READY,&vcc->flags); + close_rx(vcc); + EVENT("close_tx\n",0,0); + close_tx(vcc); + DPRINTK("zatm_close: done waiting\n"); + /* deallocate memory */ + kfree(ZATM_VCC(vcc)); + vcc->dev_data = NULL; + clear_bit(ATM_VF_ADDR,&vcc->flags); +} + + +static int zatm_open(struct atm_vcc *vcc) +{ + struct zatm_vcc *zatm_vcc; + short vpi = vcc->vpi; + int vci = vcc->vci; + int error; + + DPRINTK(">zatm_open\n"); + if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) + vcc->dev_data = NULL; + if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC) + set_bit(ATM_VF_ADDR,&vcc->flags); + if (vcc->qos.aal != ATM_AAL5) return -EINVAL; /* @@@ AAL0 */ + DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n",vcc->dev->number,vcc->vpi, + vcc->vci); + if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) { + zatm_vcc = kmalloc(sizeof(*zatm_vcc), GFP_KERNEL); + if (!zatm_vcc) { + clear_bit(ATM_VF_ADDR,&vcc->flags); + return -ENOMEM; + } + vcc->dev_data = zatm_vcc; + ZATM_VCC(vcc)->tx_chan = 0; /* for zatm_close after open_rx */ + if ((error = open_rx_first(vcc))) { + zatm_close(vcc); + return error; + } + if ((error = open_tx_first(vcc))) { + zatm_close(vcc); + return error; + } + } + if (vci == ATM_VPI_UNSPEC || vpi == ATM_VCI_UNSPEC) return 0; + if ((error = open_rx_second(vcc))) { + zatm_close(vcc); + return error; + } + if ((error = open_tx_second(vcc))) { + zatm_close(vcc); + return error; + } + set_bit(ATM_VF_READY,&vcc->flags); + return 0; +} + + +static int zatm_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flags) +{ + printk("Not yet implemented\n"); + return -ENOSYS; + /* @@@ */ +} + + +static int zatm_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) +{ + struct zatm_dev *zatm_dev; + unsigned long flags; + + zatm_dev = ZATM_DEV(dev); + switch (cmd) { + case ZATM_GETPOOLZ: + if (!capable(CAP_NET_ADMIN)) return -EPERM; + fallthrough; + case ZATM_GETPOOL: + { + struct zatm_pool_info info; + int pool; + + if (get_user(pool, + &((struct zatm_pool_req __user *) arg)->pool_num)) + return -EFAULT; + if (pool < 0 || pool > ZATM_LAST_POOL) + return -EINVAL; + pool = array_index_nospec(pool, + ZATM_LAST_POOL + 1); + spin_lock_irqsave(&zatm_dev->lock, flags); + info = zatm_dev->pool_info[pool]; + if (cmd == ZATM_GETPOOLZ) { + zatm_dev->pool_info[pool].rqa_count = 0; + zatm_dev->pool_info[pool].rqu_count = 0; + } + spin_unlock_irqrestore(&zatm_dev->lock, flags); + return copy_to_user( + &((struct zatm_pool_req __user *) arg)->info, + &info,sizeof(info)) ? -EFAULT : 0; + } + case ZATM_SETPOOL: + { + struct zatm_pool_info info; + int pool; + + if (!capable(CAP_NET_ADMIN)) return -EPERM; + if (get_user(pool, + &((struct zatm_pool_req __user *) arg)->pool_num)) + return -EFAULT; + if (pool < 0 || pool > ZATM_LAST_POOL) + return -EINVAL; + pool = array_index_nospec(pool, + ZATM_LAST_POOL + 1); + if (copy_from_user(&info, + &((struct zatm_pool_req __user *) arg)->info, + sizeof(info))) return -EFAULT; + if (!info.low_water) + info.low_water = zatm_dev-> + pool_info[pool].low_water; + if (!info.high_water) + info.high_water = zatm_dev-> + pool_info[pool].high_water; + if (!info.next_thres) + info.next_thres = zatm_dev-> + pool_info[pool].next_thres; + if (info.low_water >= info.high_water || + info.low_water < 0) + return -EINVAL; + spin_lock_irqsave(&zatm_dev->lock, flags); + zatm_dev->pool_info[pool].low_water = + info.low_water; + zatm_dev->pool_info[pool].high_water = + info.high_water; + zatm_dev->pool_info[pool].next_thres = + info.next_thres; + spin_unlock_irqrestore(&zatm_dev->lock, flags); + return 0; + } + default: + if (!dev->phy->ioctl) return -ENOIOCTLCMD; + return dev->phy->ioctl(dev,cmd,arg); + } +} + +static int zatm_send(struct atm_vcc *vcc,struct sk_buff *skb) +{ + int error; + + EVENT(">zatm_send 0x%lx\n",(unsigned long) skb,0); + if (!ZATM_VCC(vcc)->tx_chan || !test_bit(ATM_VF_READY,&vcc->flags)) { + if (vcc->pop) vcc->pop(vcc,skb); + else dev_kfree_skb(skb); + return -EINVAL; + } + if (!skb) { + printk(KERN_CRIT "!skb in zatm_send ?\n"); + if (vcc->pop) vcc->pop(vcc,skb); + return -EINVAL; + } + ATM_SKB(skb)->vcc = vcc; + error = do_tx(skb); + if (error != RING_BUSY) return error; + skb_queue_tail(&ZATM_VCC(vcc)->backlog,skb); + return 0; +} + + +static void zatm_phy_put(struct atm_dev *dev,unsigned char value, + unsigned long addr) +{ + struct zatm_dev *zatm_dev; + + zatm_dev = ZATM_DEV(dev); + zwait(); + zout(value,CER); + zout(uPD98401_IND_ACC | uPD98401_IA_B0 | + (uPD98401_IA_TGT_PHY << uPD98401_IA_TGT_SHIFT) | addr,CMR); +} + + +static unsigned char zatm_phy_get(struct atm_dev *dev,unsigned long addr) +{ + struct zatm_dev *zatm_dev; + + zatm_dev = ZATM_DEV(dev); + zwait(); + zout(uPD98401_IND_ACC | uPD98401_IA_B0 | uPD98401_IA_RW | + (uPD98401_IA_TGT_PHY << uPD98401_IA_TGT_SHIFT) | addr,CMR); + zwait(); + return zin(CER) & 0xff; +} + + +static const struct atmdev_ops ops = { + .open = zatm_open, + .close = zatm_close, + .ioctl = zatm_ioctl, + .send = zatm_send, + .phy_put = zatm_phy_put, + .phy_get = zatm_phy_get, + .change_qos = zatm_change_qos, +}; + +static int zatm_init_one(struct pci_dev *pci_dev, + const struct pci_device_id *ent) +{ + struct atm_dev *dev; + struct zatm_dev *zatm_dev; + int ret = -ENOMEM; + + zatm_dev = kmalloc(sizeof(*zatm_dev), GFP_KERNEL); + if (!zatm_dev) { + printk(KERN_EMERG "%s: memory shortage\n", DEV_LABEL); + goto out; + } + + dev = atm_dev_register(DEV_LABEL, &pci_dev->dev, &ops, -1, NULL); + if (!dev) + goto out_free; + + ret = pci_enable_device(pci_dev); + if (ret < 0) + goto out_deregister; + + ret = pci_request_regions(pci_dev, DEV_LABEL); + if (ret < 0) + goto out_disable; + + ret = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32)); + if (ret < 0) + goto out_release; + + zatm_dev->pci_dev = pci_dev; + dev->dev_data = zatm_dev; + zatm_dev->copper = (int)ent->driver_data; + if ((ret = zatm_init(dev)) || (ret = zatm_start(dev))) + goto out_release; + + pci_set_drvdata(pci_dev, dev); + zatm_dev->more = zatm_boards; + zatm_boards = dev; + ret = 0; +out: + return ret; + +out_release: + pci_release_regions(pci_dev); +out_disable: + pci_disable_device(pci_dev); +out_deregister: + atm_dev_deregister(dev); +out_free: + kfree(zatm_dev); + goto out; +} + + +MODULE_LICENSE("GPL"); + +static const struct pci_device_id zatm_pci_tbl[] = { + { PCI_VDEVICE(ZEITNET, PCI_DEVICE_ID_ZEITNET_1221), ZATM_COPPER }, + { PCI_VDEVICE(ZEITNET, PCI_DEVICE_ID_ZEITNET_1225), 0 }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, zatm_pci_tbl); + +static struct pci_driver zatm_driver = { + .name = DEV_LABEL, + .id_table = zatm_pci_tbl, + .probe = zatm_init_one, +}; + +static int __init zatm_init_module(void) +{ + return pci_register_driver(&zatm_driver); +} + +module_init(zatm_init_module); +/* module_exit not defined so not unloadable */ diff --git a/drivers/atm/zatm.h b/drivers/atm/zatm.h new file mode 100644 index 000000000..8204369fe --- /dev/null +++ b/drivers/atm/zatm.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/zatm.h - ZeitNet ZN122x device driver declarations */ + +/* Written 1995-1998 by Werner Almesberger, EPFL LRC/ICA */ + + +#ifndef DRIVER_ATM_ZATM_H +#define DRIVER_ATM_ZATM_H + +#include +#include +#include +#include +#include + + +#define DEV_LABEL "zatm" + +#define MAX_AAL5_PDU 10240 /* allocate for AAL5 PDUs of this size */ +#define MAX_RX_SIZE_LD 14 /* ceil(log2((MAX_AAL5_PDU+47)/48)) */ + +#define LOW_MARK 12 /* start adding new buffers if less than 12 */ +#define HIGH_MARK 30 /* stop adding buffers after reaching 30 */ +#define OFF_CNG_THRES 5 /* threshold for offset changes */ + +#define RX_SIZE 2 /* RX lookup entry size (in bytes) */ +#define NR_POOLS 32 /* number of free buffer pointers */ +#define POOL_SIZE 8 /* buffer entry size (in bytes) */ +#define NR_SHAPERS 16 /* number of shapers */ +#define SHAPER_SIZE 4 /* shaper entry size (in bytes) */ +#define VC_SIZE 32 /* VC dsc (TX or RX) size (in bytes) */ + +#define RING_ENTRIES 32 /* ring entries (without back pointer) */ +#define RING_WORDS 4 /* ring element size */ +#define RING_SIZE (sizeof(unsigned long)*(RING_ENTRIES+1)*RING_WORDS) + +#define NR_MBX 4 /* four mailboxes */ +#define MBX_RX_0 0 /* mailbox indices */ +#define MBX_RX_1 1 +#define MBX_TX_0 2 +#define MBX_TX_1 3 + +struct zatm_vcc { + /*-------------------------------- RX part */ + int rx_chan; /* RX channel, 0 if none */ + int pool; /* free buffer pool */ + /*-------------------------------- TX part */ + int tx_chan; /* TX channel, 0 if none */ + int shaper; /* shaper, <0 if none */ + struct sk_buff_head tx_queue; /* list of buffers in transit */ + wait_queue_head_t tx_wait; /* for close */ + u32 *ring; /* transmit ring */ + int ring_curr; /* current write position */ + int txing; /* number of transmits in progress */ + struct sk_buff_head backlog; /* list of buffers waiting for ring */ +}; + +struct zatm_dev { + /*-------------------------------- TX part */ + int tx_bw; /* remaining bandwidth */ + u32 free_shapers; /* bit set */ + int ubr; /* UBR shaper; -1 if none */ + int ubr_ref_cnt; /* number of VCs using UBR shaper */ + /*-------------------------------- RX part */ + int pool_ref[NR_POOLS]; /* free buffer pool usage counters */ + volatile struct sk_buff *last_free[NR_POOLS]; + /* last entry in respective pool */ + struct sk_buff_head pool[NR_POOLS];/* free buffer pools */ + struct zatm_pool_info pool_info[NR_POOLS]; /* pool information */ + /*-------------------------------- maps */ + struct atm_vcc **tx_map; /* TX VCCs */ + struct atm_vcc **rx_map; /* RX VCCs */ + int chans; /* map size, must be 2^n */ + /*-------------------------------- mailboxes */ + unsigned long mbx_start[NR_MBX];/* start addresses */ + dma_addr_t mbx_dma[NR_MBX]; + u16 mbx_end[NR_MBX]; /* end offset (in bytes) */ + /*-------------------------------- other pointers */ + u32 pool_base; /* Free buffer pool dsc (word addr) */ + /*-------------------------------- ZATM links */ + struct atm_dev *more; /* other ZATM devices */ + /*-------------------------------- general information */ + int mem; /* RAM on board (in bytes) */ + int khz; /* timer clock */ + int copper; /* PHY type */ + unsigned char irq; /* IRQ */ + unsigned int base; /* IO base address */ + struct pci_dev *pci_dev; /* PCI stuff */ + spinlock_t lock; +}; + + +#define ZATM_DEV(d) ((struct zatm_dev *) (d)->dev_data) +#define ZATM_VCC(d) ((struct zatm_vcc *) (d)->dev_data) + + +struct zatm_skb_prv { + struct atm_skb_data _; /* reserved */ + u32 *dsc; /* pointer to skb's descriptor */ +}; + +#define ZATM_PRV_DSC(skb) (((struct zatm_skb_prv *) (skb)->cb)->dsc) + +#endif diff --git a/drivers/atm/zeprom.h b/drivers/atm/zeprom.h new file mode 100644 index 000000000..88e01f808 --- /dev/null +++ b/drivers/atm/zeprom.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* drivers/atm/zeprom.h - ZeitNet ZN122x EEPROM (NM93C46) declarations */ + +/* Written 1995,1996 by Werner Almesberger, EPFL LRC */ + + +#ifndef DRIVER_ATM_ZEPROM_H +#define DRIVER_ATM_ZEPROM_H + +/* Different versions use different control registers */ + +#define ZEPROM_V1_REG PCI_VENDOR_ID /* PCI register */ +#define ZEPROM_V2_REG 0x40 + +/* Bits in contol register */ + +#define ZEPROM_SK 0x80000000 /* strobe (probably on raising edge) */ +#define ZEPROM_CS 0x40000000 /* Chip Select */ +#define ZEPROM_DI 0x20000000 /* Data Input */ +#define ZEPROM_DO 0x10000000 /* Data Output */ + +#define ZEPROM_SIZE 32 /* 32 bytes */ +#define ZEPROM_V1_ESI_OFF 24 /* ESI offset in EEPROM (V1) */ +#define ZEPROM_V2_ESI_OFF 4 /* ESI offset in EEPROM (V2) */ + +#define ZEPROM_CMD_LEN 3 /* commands are three bits */ +#define ZEPROM_ADDR_LEN 6 /* addresses are six bits */ + +/* Commands (3 bits) */ + +#define ZEPROM_CMD_READ 6 + +/* No other commands are needed. */ + +#endif -- cgit v1.2.3