summaryrefslogtreecommitdiffstats
path: root/drivers/atm/fore200e.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/fore200e.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/fore200e.c')
-rw-r--r--drivers/atm/fore200e.c3249
1 files changed, 3249 insertions, 0 deletions
diff --git a/drivers/atm/fore200e.c b/drivers/atm/fore200e.c
new file mode 100644
index 000000000000..196b33644627
--- /dev/null
+++ b/drivers/atm/fore200e.c
@@ -0,0 +1,3249 @@
+/*
+ $Id: fore200e.c,v 1.5 2000/04/14 10:10:34 davem Exp $
+
+ 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.
+
+ 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
+*/
+
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/capability.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/atm_suni.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/string.h>
+#include <asm/page.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#ifdef CONFIG_ATM_FORE200E_SBA
+#include <asm/idprom.h>
+#include <asm/sbus.h>
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/pgtable.h>
+#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) % (modulo))
+
+#if 1
+#define ASSERT(expr) if (!(expr)) { \
+ printk(FORE200E "assertion failed! %s[%d]: %s\n", \
+ __FUNCTION__, __LINE__, #expr); \
+ panic(FORE200E "%s", __FUNCTION__); \
+ }
+#else
+#define ASSERT(expr) do {} while (0)
+#endif
+
+
+static const struct atmdev_ops fore200e_ops;
+static const struct fore200e_bus fore200e_bus[];
+
+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)
+{
+#if defined(__sparc_v9__)
+ return __irq_itoa(irq);
+#else
+ static char str[8];
+ sprintf(str, "%d", irq);
+ return str;
+#endif
+}
+
+
+static void*
+fore200e_kmalloc(int size, int flags)
+{
+ void* chunk = kmalloc(size, flags);
+
+ if (chunk)
+ memset(chunk, 0x00, size);
+ else
+ printk(FORE200E "kmalloc() failed, requested size = %d, flags = 0x%x\n", size, flags);
+
+ return chunk;
+}
+
+
+static void
+fore200e_kfree(void* chunk)
+{
+ kfree(chunk);
+}
+
+
+/* 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->align_size = size;
+ chunk->direction = direction;
+
+ chunk->alloc_addr = fore200e_kmalloc(chunk->alloc_size, GFP_KERNEL | GFP_DMA);
+ 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 = fore200e->bus->dma_map(fore200e, chunk->align_addr, chunk->align_size, direction);
+
+ return 0;
+}
+
+
+/* free a chunk of memory */
+
+static void
+fore200e_chunk_free(struct fore200e* fore200e, struct chunk* chunk)
+{
+ fore200e->bus->dma_unmap(fore200e, chunk->dma_addr, chunk->dma_size, chunk->direction);
+
+ fore200e_kfree(chunk->alloc_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->bus->dma_chunk_free(fore200e, status);
+
+ if (rbd_block->alloc_addr)
+ fore200e->bus->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:
+ if (fore200e->stats)
+ kfree(fore200e->stats);
+
+ case FORE200E_STATE_IRQ:
+ free_irq(fore200e->irq, fore200e->atm_dev);
+
+ case FORE200E_STATE_ALLOC_BUF:
+ fore200e_free_rx_buf(fore200e);
+
+ case FORE200E_STATE_INIT_BSQ:
+ fore200e_uninit_bs_queue(fore200e);
+
+ case FORE200E_STATE_INIT_RXQ:
+ fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_rxq.status);
+ fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_rxq.rpd);
+
+ case FORE200E_STATE_INIT_TXQ:
+ fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_txq.status);
+ fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_txq.tpd);
+
+ case FORE200E_STATE_INIT_CMDQ:
+ fore200e->bus->dma_chunk_free(fore200e, &fore200e->host_cmdq.status);
+
+ case FORE200E_STATE_INITIALIZE:
+ /* nothing to do for that state */
+
+ case FORE200E_STATE_START_FW:
+ /* nothing to do for that state */
+
+ case FORE200E_STATE_LOAD_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);
+
+ 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_ATM_FORE200E_PCA
+
+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 u32
+fore200e_pca_dma_map(struct fore200e* fore200e, void* virt_addr, int size, int direction)
+{
+ u32 dma_addr = pci_map_single((struct pci_dev*)fore200e->bus_dev, virt_addr, size, direction);
+
+ DPRINTK(3, "PCI DVMA mapping: virt_addr = 0x%p, size = %d, direction = %d, --> dma_addr = 0x%08x\n",
+ virt_addr, size, direction, dma_addr);
+
+ return dma_addr;
+}
+
+
+static void
+fore200e_pca_dma_unmap(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+ DPRINTK(3, "PCI DVMA unmapping: dma_addr = 0x%08x, size = %d, direction = %d\n",
+ dma_addr, size, direction);
+
+ pci_unmap_single((struct pci_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+static void
+fore200e_pca_dma_sync_for_cpu(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+ DPRINTK(3, "PCI DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+ pci_dma_sync_single_for_cpu((struct pci_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+static void
+fore200e_pca_dma_sync_for_device(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+ DPRINTK(3, "PCI DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+ pci_dma_sync_single_for_device((struct pci_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+/* 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_pca_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 = pci_alloc_consistent((struct pci_dev*)fore200e->bus_dev,
+ chunk->alloc_size,
+ &chunk->dma_addr);
+
+ if ((chunk->alloc_addr == NULL) || (chunk->dma_addr == 0))
+ return -ENOMEM;
+
+ chunk->align_addr = chunk->alloc_addr;
+
+ return 0;
+}
+
+
+/* free a DMA consistent chunk of memory */
+
+static void
+fore200e_pca_dma_chunk_free(struct fore200e* fore200e, struct chunk* chunk)
+{
+ pci_free_consistent((struct pci_dev*)fore200e->bus_dev,
+ chunk->alloc_size,
+ chunk->alloc_addr,
+ chunk->dma_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 __init
+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 __init
+fore200e_pca_configure(struct fore200e* fore200e)
+{
+ struct pci_dev* pci_dev = (struct pci_dev*)fore200e->bus_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 = fore200e->bus->dma_map(fore200e, prom, sizeof(struct prom_data), DMA_FROM_DEVICE);
+
+ 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;
+
+ fore200e->bus->dma_unmap(fore200e, 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 = (struct pci_dev*)fore200e->bus_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));
+}
+
+#endif /* CONFIG_ATM_FORE200E_PCA */
+
+
+#ifdef CONFIG_ATM_FORE200E_SBA
+
+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 u32
+fore200e_sba_dma_map(struct fore200e* fore200e, void* virt_addr, int size, int direction)
+{
+ u32 dma_addr = sbus_map_single((struct sbus_dev*)fore200e->bus_dev, virt_addr, size, direction);
+
+ DPRINTK(3, "SBUS DVMA mapping: virt_addr = 0x%p, size = %d, direction = %d --> dma_addr = 0x%08x\n",
+ virt_addr, size, direction, dma_addr);
+
+ return dma_addr;
+}
+
+
+static void
+fore200e_sba_dma_unmap(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+ DPRINTK(3, "SBUS DVMA unmapping: dma_addr = 0x%08x, size = %d, direction = %d,\n",
+ dma_addr, size, direction);
+
+ sbus_unmap_single((struct sbus_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+static void
+fore200e_sba_dma_sync_for_cpu(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+ DPRINTK(3, "SBUS DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+ sbus_dma_sync_single_for_cpu((struct sbus_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+static void
+fore200e_sba_dma_sync_for_device(struct fore200e* fore200e, u32 dma_addr, int size, int direction)
+{
+ DPRINTK(3, "SBUS DVMA sync: dma_addr = 0x%08x, size = %d, direction = %d\n", dma_addr, size, direction);
+
+ sbus_dma_sync_single_for_device((struct sbus_dev*)fore200e->bus_dev, dma_addr, size, direction);
+}
+
+
+/* allocate a DVMA 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_sba_dma_chunk_alloc(struct fore200e* fore200e, struct chunk* chunk,
+ int size, int nbr, int alignment)
+{
+ chunk->alloc_size = chunk->align_size = size * nbr;
+
+ /* returned chunks are page-aligned */
+ chunk->alloc_addr = sbus_alloc_consistent((struct sbus_dev*)fore200e->bus_dev,
+ chunk->alloc_size,
+ &chunk->dma_addr);
+
+ if ((chunk->alloc_addr == NULL) || (chunk->dma_addr == 0))
+ return -ENOMEM;
+
+ chunk->align_addr = chunk->alloc_addr;
+
+ return 0;
+}
+
+
+/* free a DVMA consistent chunk of memory */
+
+static void
+fore200e_sba_dma_chunk_free(struct fore200e* fore200e, struct chunk* chunk)
+{
+ sbus_free_consistent((struct sbus_dev*)fore200e->bus_dev,
+ chunk->alloc_size,
+ chunk->alloc_addr,
+ chunk->dma_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 sbus_dev* sbus_dev = (struct sbus_dev*)fore200e->bus_dev;
+ unsigned int bursts;
+
+ /* gain access to the SBA specific registers */
+ fore200e->regs.sba.hcr = sbus_ioremap(&sbus_dev->resource[0], 0, SBA200E_HCR_LENGTH, "SBA HCR");
+ fore200e->regs.sba.bsr = sbus_ioremap(&sbus_dev->resource[1], 0, SBA200E_BSR_LENGTH, "SBA BSR");
+ fore200e->regs.sba.isr = sbus_ioremap(&sbus_dev->resource[2], 0, SBA200E_ISR_LENGTH, "SBA ISR");
+ fore200e->virt_base = sbus_ioremap(&sbus_dev->resource[3], 0, SBA200E_RAM_LENGTH, "SBA RAM");
+
+ if (fore200e->virt_base == NULL) {
+ 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 = prom_getintdefault(sbus_dev->bus->prom_node, "burst-sizes", 0x00);
+
+ if (sbus_can_dma_64bit(sbus_dev))
+ sbus_set_sbus64(sbus_dev, bursts);
+
+ fore200e->state = FORE200E_STATE_MAP;
+ return 0;
+}
+
+
+static void
+fore200e_sba_unmap(struct fore200e* fore200e)
+{
+ sbus_iounmap(fore200e->regs.sba.hcr, SBA200E_HCR_LENGTH);
+ sbus_iounmap(fore200e->regs.sba.bsr, SBA200E_BSR_LENGTH);
+ sbus_iounmap(fore200e->regs.sba.isr, SBA200E_ISR_LENGTH);
+ sbus_iounmap(fore200e->virt_base, SBA200E_RAM_LENGTH);
+}
+
+
+static int __init
+fore200e_sba_configure(struct fore200e* fore200e)
+{
+ fore200e->state = FORE200E_STATE_CONFIGURE;
+ return 0;
+}
+
+
+static struct fore200e* __init
+fore200e_sba_detect(const struct fore200e_bus* bus, int index)
+{
+ struct fore200e* fore200e;
+ struct sbus_bus* sbus_bus;
+ struct sbus_dev* sbus_dev = NULL;
+
+ unsigned int count = 0;
+
+ for_each_sbus (sbus_bus) {
+ for_each_sbusdev (sbus_dev, sbus_bus) {
+ if (strcmp(sbus_dev->prom_name, SBA200E_PROM_NAME) == 0) {
+ if (count >= index)
+ goto found;
+ count++;
+ }
+ }
+ }
+ return NULL;
+
+ found:
+ if (sbus_dev->num_registers != 4) {
+ printk(FORE200E "this %s device has %d instead of 4 registers\n",
+ bus->model_name, sbus_dev->num_registers);
+ return NULL;
+ }
+
+ fore200e = fore200e_kmalloc(sizeof(struct fore200e), GFP_KERNEL);
+ if (fore200e == NULL)
+ return NULL;
+
+ fore200e->bus = bus;
+ fore200e->bus_dev = sbus_dev;
+ fore200e->irq = sbus_dev->irqs[ 0 ];
+
+ fore200e->phys_base = (unsigned long)sbus_dev;
+
+ sprintf(fore200e->name, "%s-%d", bus->model_name, index - 1);
+
+ return fore200e;
+}
+
+
+static int __init
+fore200e_sba_prom_read(struct fore200e* fore200e, struct prom_data* prom)
+{
+ struct sbus_dev* sbus_dev = (struct sbus_dev*) fore200e->bus_dev;
+ int len;
+
+ len = prom_getproperty(sbus_dev->prom_node, "macaddrlo2", &prom->mac_addr[ 4 ], 4);
+ if (len < 0)
+ return -EBUSY;
+
+ len = prom_getproperty(sbus_dev->prom_node, "macaddrhi4", &prom->mac_addr[ 2 ], 4);
+ if (len < 0)
+ return -EBUSY;
+
+ prom_getproperty(sbus_dev->prom_node, "serialnumber",
+ (char*)&prom->serial_number, sizeof(prom->serial_number));
+
+ prom_getproperty(sbus_dev->prom_node, "promversion",
+ (char*)&prom->hw_revision, sizeof(prom->hw_revision));
+
+ return 0;
+}
+
+
+static int
+fore200e_sba_proc_read(struct fore200e* fore200e, char *page)
+{
+ struct sbus_dev* sbus_dev = (struct sbus_dev*)fore200e->bus_dev;
+
+ return sprintf(page, " SBUS slot/device:\t\t%d/'%s'\n", sbus_dev->slot, sbus_dev->prom_name);
+}
+#endif /* CONFIG_ATM_FORE200E_SBA */
+
+
+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 */
+ if (entry->data)
+ kfree(entry->data);
+
+ /* remove DMA mapping */
+ fore200e->bus->dma_unmap(fore200e, 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);
+ }
+#if 1
+ /* race fixed by the above incarnation mechanism, but... */
+ if (atomic_read(&sk_atm(vcc)->sk_wmem_alloc) < 0) {
+ atomic_set(&sk_atm(vcc)->sk_wmem_alloc, 0);
+ }
+#endif
+ /* 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);
+ }
+ }
+ }
+}