summaryrefslogtreecommitdiffstats
path: root/drivers/atm/horizon.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/atm/horizon.c
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/atm/horizon.c')
-rw-r--r--drivers/atm/horizon.c2953
1 files changed, 2953 insertions, 0 deletions
diff --git a/drivers/atm/horizon.c b/drivers/atm/horizon.c
new file mode 100644
index 000000000000..924a2c8988bd
--- /dev/null
+++ b/drivers/atm/horizon.c
@@ -0,0 +1,2953 @@
+/*
+ Madge Horizon ATM Adapter driver.
+ Copyright (C) 1995-1999 Madge Networks Ltd.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ The GNU GPL is contained in /usr/doc/copyright/GPL on a Debian
+ system and in the file COPYING in the Linux kernel source.
+*/
+
+/*
+ IMPORTANT NOTE: Madge Networks no longer makes the adapters
+ supported by this driver and makes no commitment to maintain it.
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/skbuff.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/uio.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/wait.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <asm/string.h>
+#include <asm/byteorder.h>
+
+#include "horizon.h"
+
+#define maintainer_string "Giuliano Procida at Madge Networks <gprocida@madge.com>"
+#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, upto pcr at mbs too
+
+ Non Real Time
+
+ nrtVBR(pcr,scr,mbs) scr bandwidth always available, upto pcr at mbs too
+ UBR()
+ ABR(mcr,pcr) mcr bandwidth always available, upto 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 (unsigned long arg);
+
+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 inline 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 inline 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;
+}
+
+static inline u16 query_tx_channel_config (hrz_dev * dev, short chan, u8 mode) {
+ wr_regw (dev, TX_CHANNEL_CONFIG_COMMAND_OFF,
+ chan * TX_CHANNEL_CONFIG_MULT | mode);
+ return rd_regw (dev, TX_CHANNEL_CONFIG_DATA_OFF);
+}
+
+/********** 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; i<skb->len && 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 channel_to_vpivci (const u16 channel, short * vpi, int * vci) {
+ unsigned short vci_bits = 10 - vpi_bits;
+ if ((channel & RX_CHANNEL_MASK) == channel) {
+ *vci = channel & ((~0)<<vci_bits);
+ *vpi = channel >> vci_bits;
+ return channel ? 0 : -EINVAL;
+ }
+ return -EINVAL;
+}
+
+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<<vpi_bits && 0 <= vci && vci < 1<<vci_bits) {
+ *channel = vpi<<vci_bits | vci;
+ return *channel ? 0 : -EINVAL;
+ }
+ return -EINVAL;
+}
+
+/********** decode RX queue entries **********/
+
+static inline u16 rx_q_entry_to_length (u32 x) {
+ return x & RX_Q_ENTRY_LENGTH_MASK;
+}
+
+static inline u16 rx_q_entry_to_rx_channel (u32 x) {
+ return (x>>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) <<br_exp == br and
+ // br_exp <= CR_MAXPEXP+CR_MIND
+
+ if (br_man <= (c << (CR_MAXPEXP+CR_MIND-br_exp))) {
+ // Equivalent to: B <= (c << (MAXPEXP+MIND))
+ // take care of rounding
+ switch (r) {
+ case round_down:
+ pre = (br+(c<<div)-1)/(c<<div);
+ // but p must be non-zero
+ if (!pre)
+ pre = 1;
+ break;
+ case round_nearest:
+ pre = (br+(c<<div)/2)/(c<<div);
+ // but p must be non-zero
+ if (!pre)
+ pre = 1;
+ break;
+ default: /* round_up */
+ pre = br/(c<<div);
+ // but p must be non-zero
+ if (!pre)
+ return -EINVAL;
+ }
+ PRINTD (DBG_QOS, "A: p=%u, d=%u", pre, div);
+ goto got_it;
+ }
+
+ // at this point we have
+ // d == MIND and (c << (MAXPEXP+MIND)) < B
+ while (div < CR_MAXD) {
+ div++;
+ if (br_man <= (c << (CR_MAXPEXP+div-br_exp))) {
+ // Equivalent to: B <= (c << (MAXPEXP+d))
+ // c << (MAXPEXP+d-1) < B <= c << (MAXPEXP+d)
+ // 1 << (MAXPEXP-1) < B/2^d/c <= 1 << MAXPEXP
+ // MAXP/2 < B/c2^d <= MAXP
+ // take care of rounding
+ switch (r) {
+ case round_down:
+ pre = (br+(c<<div)-1)/(c<<div);
+ break;
+ case round_nearest:
+ pre = (br+(c<<div)/2)/(c<<div);
+ break;
+ default: /* round_up */
+ pre = br/(c<<div);
+ }
+ PRINTD (DBG_QOS, "B: p=%u, d=%u", pre, div);
+ goto got_it;
+ }
+ }
+ // at this point we have
+ // d == MAXD and (c << (MAXPEXP+MAXD)) < B
+ // but we cannot go any higher
+ // take care of rounding
+ if (r == round_down)
+ return -EINVAL;
+ pre = 1 << CR_MAXPEXP;
+ PRINTD (DBG_QOS, "C: p=%u, d=%u", pre, div);
+got_it:
+ // paranoia
+ if (div > CR_MAXD || (!pre) || pre > 1<<CR_MAXPEXP) {
+ PRINTD (DBG_QOS, "set_cr internal failure: d=%u p=%u",
+ div, pre);
+ return -EINVAL;
+ } else {
+ if (bits)
+ *bits = (div<<CLOCK_SELECT_SHIFT) | (pre-1);
+ if (actual) {
+ *actual = (br + (pre<<div) - 1) / (pre<<div);
+ PRINTD (DBG_QOS, "actual rate: %u", *actual);
+ }
+ return 0;
+ }
+}
+
+static int make_rate_with_tolerance (const hrz_dev * dev, u32 c, rounding r, unsigned int tol,
+ u16 * bit_pattern, unsigned int * actual) {
+ unsigned int my_actual;
+
+ PRINTD (DBG_QOS|DBG_FLOW, "make_rate_with_tolerance c=%u, %s, tol=%u",
+ c, (r == round_up) ? "up" : (r == round_down) ? "down" : "nearest", tol);
+
+ if (!actual)
+ // actual rate is not returned
+ actual = &my_actual;
+
+ if (make_rate (dev, c, round_nearest, bit_pattern, actual))
+ // should never happen as round_nearest always succeeds
+ return -1;
+
+ if (c - tol <= *actual && *actual <= c + tol)
+ // within tolerance
+ return 0;
+ else
+ // intolerant, try rounding instead
+ return make_rate (dev, c, r, bit_pattern, actual);
+}
+
+/********** Listen on a VC **********/
+
+static int hrz_open_rx (hrz_dev * dev, u16 channel) {
+ // is there any guarantee that we don't get two simulataneous
+ // identical calls of this function from different processes? yes
+ // rate_lock
+ unsigned long flags;
+ u32 channel_type; // u16?
+
+ u16 buf_ptr = RX_CHANNEL_IDLE;
+
+ rx_ch_desc * rx_desc = &memmap->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 inline 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, alledgedly
+
+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);
+ do_gettimeofday(&skb->stamp);
+ // end of our responsability
+ vcc->push (vcc, skb);
+ }
+ }
+ }
+
+