/* * Hardware-level driver for the COMX and HICOMX cards * for Linux kernel 2.2.X * * Original authors: Arpad Bakay , * Peter Bajan , * Rewritten by: Tivadar Szemethy * Currently maintained by: Gergely Madarasz * * Copyright (C) 1995-2000 ITConsult-Pro Co. * * Contributors: * Arnaldo Carvalho de Melo - 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 #include #include #include #include #include #include #include #include #include #include #include "comx.h" #include "comxhw.h" MODULE_AUTHOR("Gergely Madarasz , Tivadar Szemethy , 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; } } savep = ch->HW_access_board(dev); COMX_WRITE(dev, OFF_A_L2_LINKUP, 0); if (ch->HW_set_clock) { ch->HW_set_clock(dev); } COMX_CMD(dev, COMX_CMD_INIT); jiffs = jiffies; while (COMX_readw(dev, OFF_A_L2_LINKUP) != 1 && jiffies < jiffs + HZ) { schedule_timeout(1); } if (jiffies >= jiffs + HZ) { printk(KERN_ERR "%s: board timeout on INIT command\n", dev->name); ch->HW_release_board(dev, savep); retval=-EIO; goto error; } udelay(1000); COMX_CMD(dev, COMX_CMD_OPEN); jiffs = jiffies; while (COMX_readw(dev, OFF_A_L2_LINKUP) != 3 && jiffies < jiffs + HZ) { schedule_timeout(1); } if (jiffies >= jiffs + HZ) { printk(KERN_ERR "%s: board timeout on OPEN command\n", dev->name); ch->HW_release_board(dev, savep); retval=-EIO; goto error; } ch->init_status |= HW_OPEN; /* Ez eleg ciki, de ilyen a rendszer */ if (COMX_readw(dev, OFF_A_L1_PBUFOVR) >> 8) { ch->line_status &= ~LINE_UP; } else { ch->line_status |= LINE_UP; } if (ch->LINE_status) { ch->LINE_status(dev, ch->line_status); } ch->HW_release_board(dev, savep); for ( ; procfile ; procfile = procfile->next) { if (strcmp(procfile->name, FILENAME_IRQ) == 0 || strcmp(procfile->name, FILENAME_IO) == 0 || strcmp(procfile->name, FILENAME_MEMADDR) == 0 || strcmp(procfile->name, FILENAME_CHANNEL) == 0 || strcmp(procfile->name, FILENAME_FIRMWARE) == 0 || strcmp(procfile->name, FILENAME_CLOCK) == 0) { procfile->mode = S_IFREG | 0444; } } return 0; error: if(!twin_open) { release_region(dev->base_addr, hw->io_extent); free_irq(dev->irq, (void *)dev); } return retval; } static int COMX_close(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct proc_dir_entry *procfile = ch->procdir->subdir; struct comx_privdata *hw = ch->HW_privdata; struct comx_channel *twin_ch; struct net_device *savep; savep = ch->HW_access_board(dev); COMX_CMD(dev, COMX_CMD_CLOSE); udelay(1000); COMX_CMD(dev, COMX_CMD_EXIT); ch->HW_release_board(dev, savep); if (ch->init_status & IRQ_ALLOCATED) { free_irq(dev->irq, (void *)dev); ch->init_status &= ~IRQ_ALLOCATED; } release_region(dev->base_addr, hw->io_extent); if (ch->twin && (twin_ch = ch->twin->priv) && (twin_ch->init_status & HW_OPEN)) { /* Pass the irq to the twin */ if (request_irq(dev->irq, COMX_interrupt, 0, ch->twin->name, (void *)ch->twin) == 0) { twin_ch->init_status |= IRQ_ALLOCATED; } } for ( ; procfile ; procfile = procfile->next) { if (strcmp(procfile->name, FILENAME_IRQ) == 0 || strcmp(procfile->name, FILENAME_IO) == 0 || strcmp(procfile->name, FILENAME_MEMADDR) == 0 || strcmp(procfile->name, FILENAME_CHANNEL) == 0 || strcmp(procfile->name, FILENAME_FIRMWARE) == 0 || strcmp(procfile->name, FILENAME_CLOCK) == 0) { procfile->mode = S_IFREG | 0644; } } ch->init_status &= ~HW_OPEN; return 0; } static int COMX_statistics(struct net_device *dev, char *page) { struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; struct net_device *savep; int len = 0; savep = ch->HW_access_board(dev); len += sprintf(page + len, "Board data: %s %s %s %s\nPBUFOVR: %02x, " "MODSTAT: %02x, LINKUP: %02x, DAV: %02x\nRxBUFP: %02x, " "TxEMPTY: %02x, TxBUFP: %02x\n", (ch->init_status & HW_OPEN) ? "HW_OPEN" : "", (ch->init_status & LINE_OPEN) ? "LINE_OPEN" : "", (ch->init_status & FW_LOADED) ? "FW_LOADED" : "", (ch->init_status & IRQ_ALLOCATED) ? "IRQ_ALLOCATED" : "", COMX_readw(dev, OFF_A_L1_PBUFOVR) & 0xff, (COMX_readw(dev, OFF_A_L1_PBUFOVR) >> 8) & 0xff, COMX_readw(dev, OFF_A_L2_LINKUP) & 0xff, COMX_readw(dev, OFF_A_L2_DAV) & 0xff, COMX_readw(dev, OFF_A_L2_RxBUFP) & 0xff, COMX_readw(dev, OFF_A_L2_TxEMPTY) & 0xff, COMX_readw(dev, OFF_A_L2_TxBUFP) & 0xff); len += sprintf(page + len, "hist[0]: %8lu hist[1]: %8lu hist[2]: %8lu\n" "hist[3]: %8lu hist[4]: %8lu\n",hw->histogram[0],hw->histogram[1], hw->histogram[2],hw->histogram[3],hw->histogram[4]); ch->HW_release_board(dev, savep); return len; } static int COMX_load_board(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; struct comx_firmware *fw = hw->firmware; word board_segment = dev->mem_start >> 16; int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16; unsigned long flags; unsigned char id1, id2; struct net_device *saved; int retval; int loopcount; int len; byte *COMX_address; if (!fw || !fw->len) { struct comx_channel *twin_ch = ch->twin ? ch->twin->priv : NULL; struct comx_privdata *twin_hw; if (!twin_ch || !(twin_hw = twin_ch->HW_privdata)) { return -EAGAIN; } if (!(fw = twin_hw->firmware) || !fw->len) { return -EAGAIN; } } id1 = fw->data[OFF_FW_L1_ID]; id2 = fw->data[OFF_FW_L1_ID + 1]; if (id1 != FW_L1_ID_1 || id2 != FW_L1_ID_2_COMX) { printk(KERN_ERR "%s: incorrect firmware, load aborted\n", dev->name); return -EAGAIN; } printk(KERN_INFO "%s: Loading COMX Layer 1 firmware %s\n", dev->name, (char *)(fw->data + OFF_FW_L1_ID + 2)); id1 = fw->data[OFF_FW_L2_ID]; id2 = fw->data[OFF_FW_L2_ID + 1]; if (id1 == FW_L2_ID_1 && (id2 == 0xc0 || id2 == 0xc1 || id2 == 0xc2)) { printk(KERN_INFO "with Layer 2 code %s\n", (char *)(fw->data + OFF_FW_L2_ID + 2)); } outb_p(board_segment | COMX_BOARD_RESET, dev->base_addr); /* 10 usec should be enough here */ udelay(100); save_flags(flags); cli(); saved=memory_used[mempos]; if(saved) { ((struct comx_channel *)saved->priv)->HW_board_off(saved); } memory_used[mempos]=dev; outb_p(board_segment | COMX_ENABLE_BOARD_MEM, dev->base_addr); writeb(0, dev->mem_start + COMX_JAIL_OFFSET); loopcount=0; while(loopcount++ < 10000 && readb(dev->mem_start + COMX_JAIL_OFFSET) != COMX_JAIL_VALUE) { udelay(100); } if (readb(dev->mem_start + COMX_JAIL_OFFSET) != COMX_JAIL_VALUE) { printk(KERN_ERR "%s: Can't reset board, JAIL value is %02x\n", dev->name, readb(dev->mem_start + COMX_JAIL_OFFSET)); retval=-ENODEV; goto out; } writeb(0x55, dev->mem_start + 0x18ff); loopcount=0; while(loopcount++ < 10000 && readb(dev->mem_start + 0x18ff) != 0) { udelay(100); } if(readb(dev->mem_start + 0x18ff) != 0) { printk(KERN_ERR "%s: Can't reset board, reset timeout\n", dev->name); retval=-ENODEV; goto out; } len = 0; COMX_address = (byte *)dev->mem_start; while (fw->len > len) { writeb(fw->data[len++], COMX_address++); } len = 0; COMX_address = (byte *)dev->mem_start; while (len != fw->len && readb(COMX_address++) == fw->data[len]) { len++; } if (len != fw->len) { printk(KERN_ERR "%s: error loading firmware: [%d] is 0x%02x " "instead of 0x%02x\n", dev->name, len, readb(COMX_address - 1), fw->data[len]); retval=-EAGAIN; goto out; } writeb(0, dev->mem_start + COMX_JAIL_OFFSET); loopcount = 0; while ( loopcount++ < 10000 && COMX_readw(dev, OFF_A_L2_LINKUP) != 1 ) { udelay(100); } if (COMX_readw(dev, OFF_A_L2_LINKUP) != 1) { printk(KERN_ERR "%s: error starting firmware, linkup word is %04x\n", dev->name, COMX_readw(dev, OFF_A_L2_LINKUP)); retval=-EAGAIN; goto out; } ch->init_status |= FW_LOADED; retval=0; out: outb_p(board_segment | COMX_DISABLE_ALL, dev->base_addr); if(saved) { ((struct comx_channel *)saved->priv)->HW_board_on(saved); } memory_used[mempos]=saved; restore_flags(flags); return retval; } static int CMX_load_board(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; struct comx_firmware *fw = hw->firmware; word board_segment = dev->mem_start >> 16; int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16; #if 0 unsigned char id1, id2; #endif struct net_device *saved; unsigned long flags; int retval; int loopcount; int len; byte *COMX_address; if (!fw || !fw->len) { struct comx_channel *twin_ch = ch->twin ? ch->twin->priv : NULL; struct comx_privdata *twin_hw; if (!twin_ch || !(twin_hw = twin_ch->HW_privdata)) { return -EAGAIN; } if (!(fw = twin_hw->firmware) || !fw->len) { return -EAGAIN; } } /* Ide kell olyat tenni, hogy ellenorizze az ID-t */ if (inb_p(dev->base_addr) != CMX_ID_BYTE) { printk(KERN_ERR "%s: CMX id byte is invalid(%02x)\n", dev->name, inb_p(dev->base_addr)); return -ENODEV; } printk(KERN_INFO "%s: Loading CMX Layer 1 firmware %s\n", dev->name, (char *)(fw->data + OFF_FW_L1_ID + 2)); save_flags(flags); cli(); saved=memory_used[mempos]; if(saved) { ((struct comx_channel *)saved->priv)->HW_board_off(saved); } memory_used[mempos]=dev; outb_p(board_segment | COMX_ENABLE_BOARD_MEM | COMX_BOARD_RESET, dev->base_addr); len = 0; COMX_address = (byte *)dev->mem_start; while (fw->len > len) { writeb(fw->data[len++], COMX_address++); } len = 0; COMX_address = (byte *)dev->mem_start; while (len != fw->len && readb(COMX_address++) == fw->data[len]) { len++; } outb_p(board_segment | COMX_ENABLE_BOARD_MEM, dev->base_addr); if (len != fw->len) { printk(KERN_ERR "%s: error loading firmware: [%d] is 0x%02x " "instead of 0x%02x\n", dev->name, len, readb(COMX_address - 1), fw->data[len]); retval=-EAGAIN; goto out; } loopcount=0; while( loopcount++ < 10000 && COMX_readw(dev, OFF_A_L2_LINKUP) != 1 ) { udelay(100); } if (COMX_readw(dev, OFF_A_L2_LINKUP) != 1) { printk(KERN_ERR "%s: error starting firmware, linkup word is %04x\n", dev->name, COMX_readw(dev, OFF_A_L2_LINKUP)); retval=-EAGAIN; goto out; } ch->init_status |= FW_LOADED; retval=0; out: outb_p(board_segment | COMX_DISABLE_ALL, dev->base_addr); if(saved) { ((struct comx_channel *)saved->priv)->HW_board_on(saved); } memory_used[mempos]=saved; restore_flags(flags); return retval; } static int HICOMX_load_board(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; struct comx_firmware *fw = hw->firmware; word board_segment = dev->mem_start >> 12; int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16; struct net_device *saved; unsigned char id1, id2; unsigned long flags; int retval; int loopcount; int len; word *HICOMX_address; char id = 1; if (!fw || !fw->len) { struct comx_channel *twin_ch = ch->twin ? ch->twin->priv : NULL; struct comx_privdata *twin_hw; if (!twin_ch || !(twin_hw = twin_ch->HW_privdata)) { return -EAGAIN; } if (!(fw = twin_hw->firmware) || !fw->len) { return -EAGAIN; } } while (id != 4) { if (inb_p(dev->base_addr + id++) != HICOMX_ID_BYTE) { break; } } if (id != 4) { printk(KERN_ERR "%s: can't find HICOMX at 0x%04x, id[%d] = %02x\n", dev->name, (unsigned int)dev->base_addr, id - 1, inb_p(dev->base_addr + id - 1)); return -1; } id1 = fw->data[OFF_FW_L1_ID]; id2 = fw->data[OFF_FW_L1_ID + 1]; if (id1 != FW_L1_ID_1 || id2 != FW_L1_ID_2_HICOMX) { printk(KERN_ERR "%s: incorrect firmware, load aborted\n", dev->name); return -EAGAIN; } printk(KERN_INFO "%s: Loading HICOMX Layer 1 firmware %s\n", dev->name, (char *)(fw->data + OFF_FW_L1_ID + 2)); id1 = fw->data[OFF_FW_L2_ID]; id2 = fw->data[OFF_FW_L2_ID + 1]; if (id1 == FW_L2_ID_1 && (id2 == 0xc0 || id2 == 0xc1 || id2 == 0xc2)) { printk(KERN_INFO "with Layer 2 code %s\n", (char *)(fw->data + OFF_FW_L2_ID + 2)); } outb_p(board_segment | HICOMX_BOARD_RESET, dev->base_addr); udelay(10); save_flags(flags); cli(); saved=memory_used[mempos]; if(saved) { ((struct comx_channel *)saved->priv)->HW_board_off(saved); } memory_used[mempos]=dev; outb_p(board_segment | HICOMX_ENABLE_BOARD_MEM, dev->base_addr); outb_p(HICOMX_PRG_MEM, dev->base_addr + 1); len = 0; HICOMX_address = (word *)dev->mem_start; while (fw->len > len) { writeb(fw->data[len++], HICOMX_address++); } len = 0; HICOMX_address = (word *)dev->mem_start; while (len != fw->len && (readw(HICOMX_address++) & 0xff) == fw->data[len]) { len++; } if (len != fw->len) { printk(KERN_ERR "%s: error loading firmware: [%d] is 0x%02x " "instead of 0x%02x\n", dev->name, len, readw(HICOMX_address - 1) & 0xff, fw->data[len]); retval=-EAGAIN; goto out; } outb_p(board_segment | HICOMX_BOARD_RESET, dev->base_addr); outb_p(HICOMX_DATA_MEM, dev->base_addr + 1); outb_p(board_segment | HICOMX_ENABLE_BOARD_MEM, dev->base_addr); loopcount=0; while(loopcount++ < 10000 && COMX_readw(dev, OFF_A_L2_LINKUP) != 1) { udelay(100); } if ( COMX_readw(dev, OFF_A_L2_LINKUP) != 1 ) { printk(KERN_ERR "%s: error starting firmware, linkup word is %04x\n", dev->name, COMX_readw(dev, OFF_A_L2_LINKUP)); retval=-EAGAIN; goto out; } ch->init_status |= FW_LOADED; retval=0; out: outb_p(board_segment | HICOMX_DISABLE_ALL, dev->base_addr); outb_p(HICOMX_DATA_MEM, dev->base_addr + 1); if(saved) { ((struct comx_channel *)saved->priv)->HW_board_on(saved); } memory_used[mempos]=saved; restore_flags(flags); return retval; } static struct net_device *comx_twin_check(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct proc_dir_entry *procfile = ch->procdir->parent->subdir; struct comx_privdata *hw = ch->HW_privdata; struct net_device *twin; struct comx_channel *ch_twin; struct comx_privdata *hw_twin; for ( ; procfile ; procfile = procfile->next) { if(!S_ISDIR(procfile->mode)) { continue; } twin=procfile->data; ch_twin=twin->priv; hw_twin=ch_twin->HW_privdata; if (twin != dev && dev->irq && dev->base_addr && dev->mem_start && dev->irq == twin->irq && dev->base_addr == twin->base_addr && dev->mem_start == twin->mem_start && hw->channel == (1 - hw_twin->channel) && ch->hardware == ch_twin->hardware) { return twin; } } return NULL; } static int comxhw_write_proc(struct file *file, const char *buffer, u_long count, void *data) { struct proc_dir_entry *entry = (struct proc_dir_entry *)data; struct net_device *dev = entry->parent->data; struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; char *page; if(ch->init_status & HW_OPEN) { return -EAGAIN; } if (strcmp(FILENAME_FIRMWARE, entry->name) != 0) { if (!(page = (char *)__get_free_page(GFP_KERNEL))) { return -ENOMEM; } if(copy_from_user(page, buffer, count = (min_t(int, count, PAGE_SIZE)))) { count = -EFAULT; goto out; } if (page[count-1] == '\n') page[count-1] = '\0'; else if (count < PAGE_SIZE) page[count] = '\0'; else if (page[count]) { count = -EINVAL; goto out; } page[count]=0; /* Null terminate */ } else { byte *tmp; if (!hw->firmware) { if ((hw->firmware = kmalloc(sizeof(struct comx_firmware), GFP_KERNEL)) == NULL) { return -ENOMEM; } hw->firmware->len = 0; hw->firmware->data = NULL; } if ((tmp = kmalloc(count + file->f_pos, GFP_KERNEL)) == NULL) { return -ENOMEM; } /* Ha nem 0 a fpos, akkor meglevo file-t irunk. Gyenge trukk. */ if (hw->firmware && hw->firmware->len && file->f_pos && hw->firmware->len < count + file->f_pos) { memcpy(tmp, hw->firmware->data, hw->firmware->len); } if (hw->firmware->data) { kfree(hw->firmware->data); } copy_from_user(tmp + file->f_pos, buffer, count); hw->firmware->len = entry->size = file->f_pos + count; hw->firmware->data = tmp; file->f_pos += count; return count; } if (strcmp(entry->name, FILENAME_CHANNEL) == 0) { hw->channel = simple_strtoul(page, NULL, 0); if (hw->channel >= MAX_CHANNELNO) { printk(KERN_ERR "Invalid channel number\n"); hw->channel = 0; } if ((ch->twin = comx_twin_check(dev)) != NULL) { struct comx_channel *twin_ch = ch->twin->priv; twin_ch->twin = dev; } } else if (strcmp(entry->name, FILENAME_IRQ) == 0) { dev->irq = simple_strtoul(page, NULL, 0); if (dev->irq == 2) { dev->irq = 9; } if (dev->irq < 3 || dev->irq > 15) { printk(KERN_ERR "comxhw: Invalid irq number\n"); dev->irq = 0; } if ((ch->twin = comx_twin_check(dev)) != NULL) { struct comx_channel *twin_ch = ch->twin->priv; twin_ch->twin = dev; } } else if (strcmp(entry->name, FILENAME_IO) == 0) { dev->base_addr = simple_strtoul(page, NULL, 0); if ((dev->base_addr & 3) != 0 || dev->base_addr < 0x300 || dev->base_addr > 0x3fc) { printk(KERN_ERR "Invalid io value\n"); dev->base_addr = 0; } if ((ch->twin = comx_twin_check(dev)) != NULL) { struct comx_channel *twin_ch = ch->twin->priv; twin_ch->twin = dev; } } else if (strcmp(entry->name, FILENAME_MEMADDR) == 0) { dev->mem_start = simple_strtoul(page, NULL, 0); if (dev->mem_start <= 0xf000 && dev->mem_start >= 0xa000) { dev->mem_start *= 16; } if ((dev->mem_start & 0xfff) != 0 || dev->mem_start < COMX_MEM_MIN || dev->mem_start + hw->memory_size > COMX_MEM_MAX) { printk(KERN_ERR "Invalid memory page\n"); dev->mem_start = 0; } dev->mem_end = dev->mem_start + hw->memory_size; if ((ch->twin = comx_twin_check(dev)) != NULL) { struct comx_channel *twin_ch = ch->twin->priv; twin_ch->twin = dev; } } else if (strcmp(entry->name, FILENAME_CLOCK) == 0) { if (strncmp("ext", page, 3) == 0) { hw->clock = 0; } else { int kbps; kbps = simple_strtoul(page, NULL, 0); hw->clock = kbps ? COMX_CLOCK_CONST/kbps : 0; } } out: free_page((unsigned long)page); return count; } static int comxhw_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { struct proc_dir_entry *file = (struct proc_dir_entry *)data; struct net_device *dev = file->parent->data; struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; int len = 0; if (strcmp(file->name, FILENAME_IO) == 0) { len = sprintf(page, "0x%03x\n", (unsigned int)dev->base_addr); } else if (strcmp(file->name, FILENAME_IRQ) == 0) { len = sprintf(page, "0x%02x\n", dev->irq == 9 ? 2 : dev->irq); } else if (strcmp(file->name, FILENAME_CHANNEL) == 0) { len = sprintf(page, "%01d\n", hw->channel); } else if (strcmp(file->name, FILENAME_MEMADDR) == 0) { len = sprintf(page, "0x%05x\n", (unsigned int)dev->mem_start); } else if (strcmp(file->name, FILENAME_TWIN) == 0) { len = sprintf(page, "%s\n", ch->twin ? ch->twin->name : "none"); } else if (strcmp(file->name, FILENAME_CLOCK) == 0) { if (hw->clock) { len = sprintf(page, "%-8d\n", COMX_CLOCK_CONST/hw->clock); } else { len = sprintf(page, "external\n"); } } else if (strcmp(file->name, FILENAME_FIRMWARE) == 0) { len = min_t(int, FILE_PAGESIZE, min_t(int, count, hw->firmware ? (hw->firmware->len - off) : 0)); if (len < 0) { len = 0; } *start = hw->firmware ? (hw->firmware->data + off) : NULL; if (off + len >= (hw->firmware ? hw->firmware->len : 0) || len == 0) { *eof = 1; } return len; } if (off >= len) { *eof = 1; return 0; } *start = page + off; if (count >= len - off) { *eof = 1; } return min_t(int, count, len - off); } /* Called on echo comx >boardtype */ static int COMX_init(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct comx_privdata *hw; struct proc_dir_entry *new_file; if ((ch->HW_privdata = kmalloc(sizeof(struct comx_privdata), GFP_KERNEL)) == NULL) { return -ENOMEM; } memset(hw = ch->HW_privdata, 0, sizeof(struct comx_privdata)); if (ch->hardware == &comx_hw || ch->hardware == &cmx_hw) { hw->memory_size = COMX_MEMORY_SIZE; hw->io_extent = COMX_IO_EXTENT; dev->base_addr = COMX_DEFAULT_IO; dev->irq = COMX_DEFAULT_IRQ; dev->mem_start = COMX_DEFAULT_MEMADDR; dev->mem_end = COMX_DEFAULT_MEMADDR + COMX_MEMORY_SIZE; } else if (ch->hardware == &hicomx_hw) { hw->memory_size = HICOMX_MEMORY_SIZE; hw->io_extent = HICOMX_IO_EXTENT; dev->base_addr = HICOMX_DEFAULT_IO; dev->irq = HICOMX_DEFAULT_IRQ; dev->mem_start = HICOMX_DEFAULT_MEMADDR; dev->mem_end = HICOMX_DEFAULT_MEMADDR + HICOMX_MEMORY_SIZE; } else { printk(KERN_ERR "SERIOUS INTERNAL ERROR in %s, line %d\n", __FILE__, __LINE__); } if ((new_file = create_proc_entry(FILENAME_IO, S_IFREG | 0644, ch->procdir)) == NULL) { goto cleanup_HW_privdata; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = &comxhw_write_proc; new_file->size = 6; new_file->nlink = 1; if ((new_file = create_proc_entry(FILENAME_IRQ, S_IFREG | 0644, ch->procdir)) == NULL) { goto cleanup_filename_io; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = &comxhw_write_proc; new_file->size = 5; new_file->nlink = 1; if ((new_file = create_proc_entry(FILENAME_CHANNEL, S_IFREG | 0644, ch->procdir)) == NULL) { goto cleanup_filename_irq; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = &comxhw_write_proc; new_file->size = 2; // Ezt tudjuk new_file->nlink = 1; if (ch->hardware == &hicomx_hw || ch->hardware == &cmx_hw) { if ((new_file = create_proc_entry(FILENAME_CLOCK, S_IFREG | 0644, ch->procdir)) == NULL) { goto cleanup_filename_channel; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = &comxhw_write_proc; new_file->size = 9; new_file->nlink = 1; } if ((new_file = create_proc_entry(FILENAME_MEMADDR, S_IFREG | 0644, ch->procdir)) == NULL) { goto cleanup_filename_clock; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = &comxhw_write_proc; new_file->size = 8; new_file->nlink = 1; if ((new_file = create_proc_entry(FILENAME_TWIN, S_IFREG | 0444, ch->procdir)) == NULL) { goto cleanup_filename_memaddr; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = NULL; new_file->nlink = 1; if ((new_file = create_proc_entry(FILENAME_FIRMWARE, S_IFREG | 0644, ch->procdir)) == NULL) { goto cleanup_filename_twin; } new_file->data = (void *)new_file; new_file->read_proc = &comxhw_read_proc; new_file->write_proc = &comxhw_write_proc; new_file->nlink = 1; if (ch->hardware == &comx_hw) { ch->HW_board_on = COMX_board_on; ch->HW_board_off = COMX_board_off; ch->HW_load_board = COMX_load_board; } else if (ch->hardware == &cmx_hw) { ch->HW_board_on = COMX_board_on; ch->HW_board_off = COMX_board_off; ch->HW_load_board = CMX_load_board; ch->HW_set_clock = COMX_set_clock; } else if (ch->hardware == &hicomx_hw) { ch->HW_board_on = HICOMX_board_on; ch->HW_board_off = HICOMX_board_off; ch->HW_load_board = HICOMX_load_board; ch->HW_set_clock = COMX_set_clock; } else { printk(KERN_ERR "SERIOUS INTERNAL ERROR in %s, line %d\n", __FILE__, __LINE__); } ch->HW_access_board = COMX_access_board; ch->HW_release_board = COMX_release_board; ch->HW_txe = COMX_txe; ch->HW_open = COMX_open; ch->HW_close = COMX_close; ch->HW_send_packet = COMX_send_packet; ch->HW_statistics = COMX_statistics; if ((ch->twin = comx_twin_check(dev)) != NULL) { struct comx_channel *twin_ch = ch->twin->priv; twin_ch->twin = dev; } MOD_INC_USE_COUNT; return 0; cleanup_filename_twin: remove_proc_entry(FILENAME_TWIN, ch->procdir); cleanup_filename_memaddr: remove_proc_entry(FILENAME_MEMADDR, ch->procdir); cleanup_filename_clock: if (ch->hardware == &hicomx_hw || ch->hardware == &cmx_hw) remove_proc_entry(FILENAME_CLOCK, ch->procdir); cleanup_filename_channel: remove_proc_entry(FILENAME_CHANNEL, ch->procdir); cleanup_filename_irq: remove_proc_entry(FILENAME_IRQ, ch->procdir); cleanup_filename_io: remove_proc_entry(FILENAME_IO, ch->procdir); cleanup_HW_privdata: kfree(ch->HW_privdata); return -EIO; } /* Called on echo valami >boardtype */ static int COMX_exit(struct net_device *dev) { struct comx_channel *ch = dev->priv; struct comx_privdata *hw = ch->HW_privdata; if (hw->firmware) { if (hw->firmware->data) kfree(hw->firmware->data); kfree(hw->firmware); } if (ch->twin) { struct comx_channel *twin_ch = ch->twin->priv; twin_ch->twin = NULL; } kfree(ch->HW_privdata); remove_proc_entry(FILENAME_IO, ch->procdir); remove_proc_entry(FILENAME_IRQ, ch->procdir); remove_proc_entry(FILENAME_CHANNEL, ch->procdir); remove_proc_entry(FILENAME_MEMADDR, ch->procdir); remove_proc_entry(FILENAME_FIRMWARE, ch->procdir); remove_proc_entry(FILENAME_TWIN, ch->procdir); if (ch->hardware == &hicomx_hw || ch->hardware == &cmx_hw) { remove_proc_entry(FILENAME_CLOCK, ch->procdir); } MOD_DEC_USE_COUNT; return 0; } static int COMX_dump(struct net_device *dev) { printk(KERN_INFO "%s: COMX_dump called, why ?\n", dev->name); return 0; } static struct comx_hardware comx_hw = { "comx", VERSION, COMX_init, COMX_exit, COMX_dump, NULL }; static struct comx_hardware cmx_hw = { "cmx", VERSION, COMX_init, COMX_exit, COMX_dump, NULL }; static struct comx_hardware hicomx_hw = { "hicomx", VERSION, COMX_init, COMX_exit, COMX_dump, NULL }; #ifdef MODULE #define comx_hw_comx_init init_module #endif int __init comx_hw_comx_init(void) { comx_register_hardware(&comx_hw); comx_register_hardware(&cmx_hw); comx_register_hardware(&hicomx_hw); memset(memory_used, 0, sizeof(memory_used)); return 0; } #ifdef MODULE void cleanup_module(void) { comx_unregister_hardware("comx"); comx_unregister_hardware("cmx"); comx_unregister_hardware("hicomx"); } #endif