📄 comx-hw-comx.c
字号:
/* * Hardware-level driver for the COMX and HICOMX cards * for Linux kernel 2.2.X * * Original authors: Arpad Bakay <bakay.arpad@synergon.hu>, * Peter Bajan <bajan.peter@synergon.hu>, * Rewritten by: Tivadar Szemethy <tiv@itc.hu> * Currently maintained by: Gergely Madarasz <gorgo@itc.hu> * * Copyright (C) 1995-2000 ITConsult-Pro Co. <info@itc.hu> * * Contributors: * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 0.86 * * 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. * * Version 0.80 (99/06/11): * - port back to kernel, add support builtin driver * - cleaned up the source code a bit * * Version 0.81 (99/06/22): * - cleaned up the board load functions, no more long reset * timeouts * - lower modem lines on close * - some interrupt handling fixes * * Version 0.82 (99/08/24): * - fix multiple board support * * Version 0.83 (99/11/30): * - interrupt handling and locking fixes during initalization * - really fix multiple board support * * Version 0.84 (99/12/02): * - some workarounds for problematic hardware/firmware * * Version 0.85 (00/01/14): * - some additional workarounds :/ * - printk cleanups * Version 0.86 (00/08/15): * - resource release on failure at COMX_init */#define VERSION "0.86"#include <linux/module.h>#include <linux/version.h>#include <linux/types.h>#include <linux/sched.h>#include <linux/netdevice.h>#include <linux/proc_fs.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/uaccess.h>#include <asm/io.h>#include "comx.h"#include "comxhw.h"MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>, Tivadar Szemethy <tiv@itc.hu>, Arpad Bakay");MODULE_DESCRIPTION("Hardware-level driver for the COMX and HICOMX adapters\n");MODULE_LICENSE("GPL");#define COMX_readw(dev, offset) (readw(dev->mem_start + offset + \ (unsigned int)(((struct comx_privdata *)\ ((struct comx_channel *)dev->priv)->HW_privdata)->channel) \ * COMX_CHANNEL_OFFSET))#define COMX_WRITE(dev, offset, value) (writew(value, dev->mem_start + offset \ + (unsigned int)(((struct comx_privdata *) \ ((struct comx_channel *)dev->priv)->HW_privdata)->channel) \ * COMX_CHANNEL_OFFSET))#define COMX_CMD(dev, cmd) (COMX_WRITE(dev, OFF_A_L2_CMD, cmd))struct comx_firmware { int len; unsigned char *data;};struct comx_privdata { struct comx_firmware *firmware; u16 clock; char channel; // channel no. int memory_size; short io_extent; u_long histogram[5];};static struct net_device *memory_used[(COMX_MEM_MAX - COMX_MEM_MIN) / 0x10000];extern struct comx_hardware hicomx_hw;extern struct comx_hardware comx_hw;extern struct comx_hardware cmx_hw;static void COMX_interrupt(int irq, void *dev_id, struct pt_regs *regs);static void COMX_board_on(struct net_device *dev){ outb_p( (byte) (((dev->mem_start & 0xf0000) >> 16) | COMX_ENABLE_BOARD_IT | COMX_ENABLE_BOARD_MEM), dev->base_addr);}static void COMX_board_off(struct net_device *dev){ outb_p( (byte) (((dev->mem_start & 0xf0000) >> 16) | COMX_ENABLE_BOARD_IT), dev->base_addr);}static void HICOMX_board_on(struct net_device *dev){ outb_p( (byte) (((dev->mem_start & 0xf0000) >> 12) | HICOMX_ENABLE_BOARD_MEM), dev->base_addr);}static void HICOMX_board_off(struct net_device *dev){ outb_p( (byte) (((dev->mem_start & 0xf0000) >> 12) | HICOMX_DISABLE_BOARD_MEM), dev->base_addr);}static void COMX_set_clock(struct net_device *dev){ struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; COMX_WRITE(dev, OFF_A_L1_CLKINI, hw->clock);}static struct net_device *COMX_access_board(struct net_device *dev){ struct comx_channel *ch = dev->priv; struct net_device *ret; int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16; unsigned long flags; save_flags(flags); cli(); ret = memory_used[mempos]; if(ret == dev) { goto out; } memory_used[mempos] = dev; if (!ch->twin || ret != ch->twin) { if (ret) ((struct comx_channel *)ret->priv)->HW_board_off(ret); ch->HW_board_on(dev); }out: restore_flags(flags); return ret;}static void COMX_release_board(struct net_device *dev, struct net_device *savep){ unsigned long flags; int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16; struct comx_channel *ch = dev->priv; save_flags(flags); cli(); if (memory_used[mempos] == savep) { goto out; } memory_used[mempos] = savep; if (!ch->twin || ch->twin != savep) { ch->HW_board_off(dev); if (savep) ((struct comx_channel*)savep->priv)->HW_board_on(savep); }out: restore_flags(flags);}static int COMX_txe(struct net_device *dev) { struct net_device *savep; struct comx_channel *ch = dev->priv; int rc = 0; savep = ch->HW_access_board(dev); if (COMX_readw(dev,OFF_A_L2_LINKUP) == LINKUP_READY) { rc = COMX_readw(dev,OFF_A_L2_TxEMPTY); } ch->HW_release_board(dev,savep); if(rc==0xffff) { printk(KERN_ERR "%s, OFF_A_L2_TxEMPTY is %d\n",dev->name, rc); } return rc;}static int COMX_send_packet(struct net_device *dev, struct sk_buff *skb){ struct net_device *savep; struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; int ret = FRAME_DROPPED; word tmp; savep = ch->HW_access_board(dev); if (ch->debug_flags & DEBUG_HW_TX) { comx_debug_bytes(dev, skb->data, skb->len,"COMX_send packet"); } if (skb->len > COMX_MAX_TX_SIZE) { ret=FRAME_DROPPED; goto out; } tmp=COMX_readw(dev, OFF_A_L2_TxEMPTY); if ((ch->line_status & LINE_UP) && tmp==1) { int lensave = skb->len; int dest = COMX_readw(dev, OFF_A_L2_TxBUFP); word *data = (word *)skb->data; if(dest==0xffff) { printk(KERN_ERR "%s: OFF_A_L2_TxBUFP is %d\n", dev->name, dest); ret=FRAME_DROPPED; goto out; } writew((unsigned short)skb->len, dev->mem_start + dest); dest += 2; while (skb->len > 1) { writew(*data++, dev->mem_start + dest); dest += 2; skb->len -= 2; } if (skb->len == 1) { writew(*((byte *)data), dev->mem_start + dest); } writew(0, dev->mem_start + (int)hw->channel * COMX_CHANNEL_OFFSET + OFF_A_L2_TxEMPTY); ch->stats.tx_packets++; ch->stats.tx_bytes += lensave; ret = FRAME_ACCEPTED; } else { ch->stats.tx_dropped++; printk(KERN_INFO "%s: frame dropped\n",dev->name); if(tmp) { printk(KERN_ERR "%s: OFF_A_L2_TxEMPTY is %d\n",dev->name,tmp); } } out: ch->HW_release_board(dev, savep); dev_kfree_skb(skb); return ret;}static inline int comx_read_buffer(struct net_device *dev) { struct comx_channel *ch = dev->priv; word rbuf_offs; struct sk_buff *skb; word len; int i=0; word *writeptr; i = 0; rbuf_offs = COMX_readw(dev, OFF_A_L2_RxBUFP); if(rbuf_offs == 0xffff) { printk(KERN_ERR "%s: OFF_A_L2_RxBUFP is %d\n",dev->name,rbuf_offs); return 0; } len = readw(dev->mem_start + rbuf_offs); if(len > COMX_MAX_RX_SIZE) { printk(KERN_ERR "%s: packet length is %d\n",dev->name,len); return 0; } if ((skb = dev_alloc_skb(len + 16)) == NULL) { ch->stats.rx_dropped++; COMX_WRITE(dev, OFF_A_L2_DAV, 0); return 0; } rbuf_offs += 2; skb_reserve(skb, 16); skb_put(skb, len); skb->dev = dev; writeptr = (word *)skb->data; while (i < len) { *writeptr++ = readw(dev->mem_start + rbuf_offs); rbuf_offs += 2; i += 2; } COMX_WRITE(dev, OFF_A_L2_DAV, 0); ch->stats.rx_packets++; ch->stats.rx_bytes += len; if (ch->debug_flags & DEBUG_HW_RX) { comx_debug_skb(dev, skb, "COMX_interrupt receiving"); } ch->LINE_rx(dev, skb); return 1;}static inline char comx_line_change(struct net_device *dev, char linestat){ struct comx_channel *ch=dev->priv; char idle=1; if (linestat & LINE_UP) { /* Vonal fol */ if (ch->lineup_delay) { if (!test_and_set_bit(0, &ch->lineup_pending)) { ch->lineup_timer.function = comx_lineup_func; ch->lineup_timer.data = (unsigned long)dev; ch->lineup_timer.expires = jiffies + HZ*ch->lineup_delay; add_timer(&ch->lineup_timer); idle=0; } } else { idle=0; ch->LINE_status(dev, ch->line_status |= LINE_UP); } } else { /* Vonal le */ idle=0; if (test_and_clear_bit(0, &ch->lineup_pending)) { del_timer(&ch->lineup_timer); } else { ch->line_status &= ~LINE_UP; if (ch->LINE_status) { ch->LINE_status(dev, ch->line_status); } } } return idle;}static void COMX_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct net_device *dev = dev_id; struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; struct net_device *interrupted; unsigned long jiffs; char idle = 0; int count = 0; word tmp; if (dev == NULL) { printk(KERN_ERR "COMX_interrupt: irq %d for unknown device\n", irq); return; } jiffs = jiffies; interrupted = ch->HW_access_board(dev); while (!idle && count < 5000) { char channel = 0; idle = 1; while (channel < 2) { char linestat = 0; char buffers_emptied = 0; if (channel == 1) { if (ch->twin) { dev = ch->twin; ch = dev->priv; hw = ch->HW_privdata; } else { break; } } else { COMX_WRITE(dev, OFF_A_L1_REPENA, COMX_readw(dev, OFF_A_L1_REPENA) & 0xFF00); } channel++; if ((ch->init_status & (HW_OPEN | LINE_OPEN)) != (HW_OPEN | LINE_OPEN)) { continue; } /* Collect stats */ tmp = COMX_readw(dev, OFF_A_L1_ABOREC); COMX_WRITE(dev, OFF_A_L1_ABOREC, 0); if(tmp==0xffff) { printk(KERN_ERR "%s: OFF_A_L1_ABOREC is %d\n",dev->name,tmp); break; } else { ch->stats.rx_missed_errors += (tmp >> 8) & 0xff; ch->stats.rx_over_errors += tmp & 0xff; } tmp = COMX_readw(dev, OFF_A_L1_CRCREC); COMX_WRITE(dev, OFF_A_L1_CRCREC, 0); if(tmp==0xffff) { printk(KERN_ERR "%s: OFF_A_L1_CRCREC is %d\n",dev->name,tmp); break; } else { ch->stats.rx_crc_errors += (tmp >> 8) & 0xff; ch->stats.rx_missed_errors += tmp & 0xff; } if ((ch->line_status & LINE_UP) && ch->LINE_rx) { tmp=COMX_readw(dev, OFF_A_L2_DAV); while (tmp==1) { idle=0; buffers_emptied+=comx_read_buffer(dev); tmp=COMX_readw(dev, OFF_A_L2_DAV); } if(tmp) { printk(KERN_ERR "%s: OFF_A_L2_DAV is %d\n", dev->name, tmp); break; } } tmp=COMX_readw(dev, OFF_A_L2_TxEMPTY); if (tmp==1 && ch->LINE_tx) { ch->LINE_tx(dev); } if(tmp==0xffff) { printk(KERN_ERR "%s: OFF_A_L2_TxEMPTY is %d\n", dev->name, tmp); break; } if (COMX_readw(dev, OFF_A_L1_PBUFOVR) >> 8) { linestat &= ~LINE_UP; } else { linestat |= LINE_UP; } if ((linestat & LINE_UP) != (ch->line_status & LINE_UP)) { ch->stats.tx_carrier_errors++; idle &= comx_line_change(dev,linestat); } hw->histogram[(int)buffers_emptied]++; } count++; } if(count==5000) { printk(KERN_WARNING "%s: interrupt stuck\n",dev->name); } ch->HW_release_board(dev, interrupted);}static int COMX_open(struct net_device *dev){ struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; struct proc_dir_entry *procfile = ch->procdir->subdir; unsigned long jiffs; int twin_open=0; int retval; struct net_device *savep; if (!dev->base_addr || !dev->irq || !dev->mem_start) { return -ENODEV; } if (ch->twin && (((struct comx_channel *)(ch->twin->priv))->init_status & HW_OPEN)) { twin_open=1; } if (!twin_open) { if (check_region(dev->base_addr, hw->io_extent)) { return -EAGAIN; } if (request_irq(dev->irq, COMX_interrupt, 0, dev->name, (void *)dev)) { printk(KERN_ERR "comx-hw-comx: unable to obtain irq %d\n", dev->irq); return -EAGAIN; } ch->init_status |= IRQ_ALLOCATED; request_region(dev->base_addr, hw->io_extent, dev->name); if (!ch->HW_load_board || ch->HW_load_board(dev)) { ch->init_status &= ~IRQ_ALLOCATED; retval=-ENODEV; goto error; } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -