diff options
Diffstat (limited to 'drivers/staging/dgap/dgap_fep5.c')
-rw-r--r-- | drivers/staging/dgap/dgap_fep5.c | 1953 |
1 files changed, 1953 insertions, 0 deletions
diff --git a/drivers/staging/dgap/dgap_fep5.c b/drivers/staging/dgap/dgap_fep5.c new file mode 100644 index 000000000000..4464f02c9575 --- /dev/null +++ b/drivers/staging/dgap/dgap_fep5.c @@ -0,0 +1,1953 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau <Scott_Kilau at digi dot com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE! + * + * This is shared code between Digi's CVS archive and the + * Linux Kernel sources. + * Changing the source just for reformatting needlessly breaks + * our CVS diff history. + * + * Send any bug fixes/changes to: Eng.Linux at digi dot com. + * Thank you. + * + * $Id: dgap_fep5.c,v 1.2 2011/06/21 10:35:40 markh Exp $ + */ + + +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> /* For udelay */ +#include <asm/uaccess.h> /* For copy_from_user/copy_to_user */ +#include <linux/tty.h> +#include <linux/tty_flip.h> /* For tty_schedule_flip */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39) +#include <linux/sched.h> +#endif + +#include "dgap_driver.h" +#include "dgap_pci.h" +#include "dgap_fep5.h" +#include "dgap_tty.h" +#include "dgap_conf.h" +#include "dgap_parse.h" +#include "dgap_trace.h" + +/* + * Our function prototypes + */ +static void dgap_cmdw_ext(struct channel_t *ch, u16 cmd, u16 word, uint ncmds); +static int dgap_event(struct board_t *bd); + +/* + * internal variables + */ +static uint dgap_count = 500; + + +/* + * Loads the dgap.conf config file from the user. + */ +void dgap_do_config_load(uchar __user *uaddr, int len) +{ + int orig_len = len; + char *to_addr; + uchar __user *from_addr = uaddr; + char buf[U2BSIZE]; + int n; + + to_addr = dgap_config_buf = dgap_driver_kzmalloc(len + 1, GFP_ATOMIC); + if (!dgap_config_buf) { + DPR_INIT(("dgap_do_config_load - unable to allocate memory for file\n")); + dgap_driver_state = DRIVER_NEED_CONFIG_LOAD; + return; + } + + n = U2BSIZE; + while (len) { + + if (n > len) + n = len; + + if (copy_from_user((char *) &buf, from_addr, n) == -1 ) + return; + + /* Copy data from buffer to kernel memory */ + memcpy(to_addr, buf, n); + + /* increment counts */ + len -= n; + to_addr += n; + from_addr += n; + n = U2BSIZE; + } + + dgap_config_buf[orig_len] = '\0'; + + to_addr = dgap_config_buf; + dgap_parsefile(&to_addr, TRUE); + + DPR_INIT(("dgap_config_load() finish\n")); + + return; +} + + +int dgap_after_config_loaded(void) +{ + int i = 0; + int rc = 0; + + /* + * Register our ttys, now that we have the config loaded. + */ + for (i = 0; i < dgap_NumBoards; ++i) { + + /* + * Initialize KME waitqueues... + */ + init_waitqueue_head(&(dgap_Board[i]->kme_wait)); + + /* + * allocate flip buffer for board. + */ + dgap_Board[i]->flipbuf = dgap_driver_kzmalloc(MYFLIPLEN, GFP_ATOMIC); + dgap_Board[i]->flipflagbuf = dgap_driver_kzmalloc(MYFLIPLEN, GFP_ATOMIC); + } + + return (rc); +} + + + +/*======================================================================= + * + * usertoboard - copy from user space to board space. + * + *=======================================================================*/ +static int dgap_usertoboard(struct board_t *brd, char *to_addr, char __user *from_addr, int len) +{ + char buf[U2BSIZE]; + int n = U2BSIZE; + + if (!brd || brd->magic != DGAP_BOARD_MAGIC) + return(-EFAULT); + + while (len) { + if (n > len) + n = len; + + if (copy_from_user((char *) &buf, from_addr, n) == -1 ) { + return(-EFAULT); + } + + /* Copy data from buffer to card memory */ + memcpy_toio(to_addr, buf, n); + + /* increment counts */ + len -= n; + to_addr += n; + from_addr += n; + n = U2BSIZE; + } + return(0); +} + + +/* + * Copies the BIOS code from the user to the board, + * and starts the BIOS running. + */ +void dgap_do_bios_load(struct board_t *brd, uchar __user *ubios, int len) +{ + uchar *addr; + uint offset; + int i; + + if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) + return; + + DPR_INIT(("dgap_do_bios_load() start\n")); + + addr = brd->re_map_membase; + + /* + * clear POST area + */ + for (i = 0; i < 16; i++) + writeb(0, addr + POSTAREA + i); + + /* + * Download bios + */ + offset = 0x1000; + if (dgap_usertoboard(brd, addr + offset, ubios, len) == -1 ) { + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + return; + } + + writel(0x0bf00401, addr); + writel(0, (addr + 4)); + + /* Clear the reset, and change states. */ + writeb(FEPCLR, brd->re_map_port); + brd->state = WAIT_BIOS_LOAD; +} + + +/* + * Checks to see if the BIOS completed running on the card. + */ +static void dgap_do_wait_for_bios(struct board_t *brd) +{ + uchar *addr; + u16 word; + + if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) + return; + + addr = brd->re_map_membase; + word = readw(addr + POSTAREA); + + /* Check to see if BIOS thinks board is good. (GD). */ + if (word == *(u16 *) "GD") { + DPR_INIT(("GOT GD in memory, moving states.\n")); + brd->state = FINISHED_BIOS_LOAD; + return; + } + + /* Give up on board after too long of time taken */ + if (brd->wait_for_bios++ > 5000) { + u16 err1 = readw(addr + SEQUENCE); + u16 err2 = readw(addr + ERROR); + APR(("***WARNING*** %s failed diagnostics. Error #(%x,%x).\n", + brd->name, err1, err2)); + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + } +} + + +/* + * Copies the FEP code from the user to the board, + * and starts the FEP running. + */ +void dgap_do_fep_load(struct board_t *brd, uchar __user *ufep, int len) +{ + uchar *addr; + uint offset; + + if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) + return; + + addr = brd->re_map_membase; + + DPR_INIT(("dgap_do_fep_load() for board %s : start\n", brd->name)); + + /* + * Download FEP + */ + offset = 0x1000; + if (dgap_usertoboard(brd, addr + offset, ufep, len) == -1 ) { + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + return; + } + + /* + * If board is a concentrator product, we need to give + * it its config string describing how the concentrators look. + */ + if ((brd->type == PCX) || (brd->type == PEPC)) { + uchar string[100]; + uchar *config, *xconfig; + int i = 0; + + xconfig = dgap_create_config_string(brd, string); + + /* Write string to board memory */ + config = addr + CONFIG; + for (; i < CONFIGSIZE; i++, config++, xconfig++) { + writeb(*xconfig, config); + if ((*xconfig & 0xff) == 0xff) + break; + } + } + + writel(0xbfc01004, (addr + 0xc34)); + writel(0x3, (addr + 0xc30)); + + /* change states. */ + brd->state = WAIT_FEP_LOAD; + + DPR_INIT(("dgap_do_fep_load() for board %s : finish\n", brd->name)); + +} + + +/* + * Waits for the FEP to report thats its ready for us to use. + */ +static void dgap_do_wait_for_fep(struct board_t *brd) +{ + uchar *addr; + u16 word; + + if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) + return; + + addr = brd->re_map_membase; + + DPR_INIT(("dgap_do_wait_for_fep() for board %s : start. addr: %p\n", brd->name, addr)); + + word = readw(addr + FEPSTAT); + + /* Check to see if FEP is up and running now. */ + if (word == *(u16 *) "OS") { + DPR_INIT(("GOT OS in memory for board %s, moving states.\n", brd->name)); + brd->state = FINISHED_FEP_LOAD; + + /* + * Check to see if the board can support FEP5+ commands. + */ + word = readw(addr + FEP5_PLUS); + if (word == *(u16 *) "5A") { + DPR_INIT(("GOT 5A in memory for board %s, board supports extended FEP5 commands.\n", brd->name)); + brd->bd_flags |= BD_FEP5PLUS; + } + + return; + } + + /* Give up on board after too long of time taken */ + if (brd->wait_for_fep++ > 5000) { + u16 err1 = readw(addr + SEQUENCE); + u16 err2 = readw(addr + ERROR); + APR(("***WARNING*** FEPOS for %s not functioning. Error #(%x,%x).\n", + brd->name, err1, err2)); + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + } + + DPR_INIT(("dgap_do_wait_for_fep() for board %s : finish\n", brd->name)); +} + + +/* + * Physically forces the FEP5 card to reset itself. + */ +static void dgap_do_reset_board(struct board_t *brd) +{ + uchar check; + u32 check1; + u32 check2; + int i = 0; + + if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase || !brd->re_map_port) { + DPR_INIT(("dgap_do_reset_board() start. bad values. brd: %p mem: %p io: %p\n", + brd, brd ? brd->re_map_membase : 0, brd ? brd->re_map_port : 0)); + return; + } + + DPR_INIT(("dgap_do_reset_board() start. io: %p\n", brd->re_map_port)); + + /* FEPRST does not vary among supported boards */ + writeb(FEPRST, brd->re_map_port); + + for (i = 0; i <= 1000; i++) { + check = readb(brd->re_map_port) & 0xe; + if (check == FEPRST) + break; + udelay(10); + + } + if (i > 1000) { + APR(("*** WARNING *** Board not resetting... Failing board.\n")); + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + goto failed; + } + + /* + * Make sure there really is memory out there. + */ + writel(0xa55a3cc3, (brd->re_map_membase + LOWMEM)); + writel(0x5aa5c33c, (brd->re_map_membase + HIGHMEM)); + check1 = readl(brd->re_map_membase + LOWMEM); + check2 = readl(brd->re_map_membase + HIGHMEM); + + if ((check1 != 0xa55a3cc3) || (check2 != 0x5aa5c33c)) { + APR(("*** Warning *** No memory at %p for board.\n", brd->re_map_membase)); + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + goto failed; + } + + if (brd->state != BOARD_FAILED) + brd->state = FINISHED_RESET; + +failed: + DPR_INIT(("dgap_do_reset_board() finish\n")); +} + + +/* + * Sends a concentrator image into the FEP5 board. + */ +void dgap_do_conc_load(struct board_t *brd, uchar *uaddr, int len) +{ + char *vaddr; + u16 offset = 0; + struct downld_t *to_dp; + + if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) + return; + + vaddr = brd->re_map_membase; + + offset = readw((u16 *) (vaddr + DOWNREQ)); + to_dp = (struct downld_t *) (vaddr + (int) offset); + + /* + * The image was already read into kernel space, + * we do NOT need a user space read here + */ + memcpy_toio((char *) to_dp, uaddr, sizeof(struct downld_t)); + + /* Tell card we have data for it */ + writew(0, vaddr + (DOWNREQ)); + + brd->conc_dl_status = NO_PENDING_CONCENTRATOR_REQUESTS; +} + + +#define EXPANSION_ROM_SIZE (64 * 1024) +#define FEP5_ROM_MAGIC (0xFEFFFFFF) + +static void dgap_get_vpd(struct board_t *brd) +{ + u32 magic; + u32 base_offset; + u16 rom_offset; + u16 vpd_offset; + u16 image_length; + u16 i; + uchar byte1; + uchar byte2; + + /* + * Poke the magic number at the PCI Rom Address location. + * If VPD is supported, the value read from that address + * will be non-zero. + */ + magic = FEP5_ROM_MAGIC; + pci_write_config_dword(brd->pdev, PCI_ROM_ADDRESS, magic); + pci_read_config_dword(brd->pdev, PCI_ROM_ADDRESS, &magic); + + /* VPD not supported, bail */ + if (!magic) + return; + + /* + * To get to the OTPROM memory, we have to send the boards base + * address or'ed with 1 into the PCI Rom Address location. + */ + magic = brd->membase | 0x01; + pci_write_config_dword(brd->pdev, PCI_ROM_ADDRESS, magic); + pci_read_config_dword(brd->pdev, PCI_ROM_ADDRESS, &magic); + + byte1 = readb(brd->re_map_membase); + byte2 = readb(brd->re_map_membase + 1); + + /* + * If the board correctly swapped to the OTPROM memory, + * the first 2 bytes (header) should be 0x55, 0xAA + */ + if (byte1 == 0x55 && byte2 == 0xAA) { + + base_offset = 0; + + /* + * We have to run through all the OTPROM memory looking + * for the VPD offset. + */ + while (base_offset <= EXPANSION_ROM_SIZE) { + + /* + * Lots of magic numbers here. + * + * The VPD offset is located inside the ROM Data Structure. + * We also have to remember the length of each + * ROM Data Structure, so we can "hop" to the next + * entry if the VPD isn't in the current + * ROM Data Structure. + */ + rom_offset = readw(brd->re_map_membase + base_offset + 0x18); + image_length = readw(brd->re_map_membase + rom_offset + 0x10) * 512; + vpd_offset = readw(brd->re_map_membase + rom_offset + 0x08); + + /* Found the VPD entry */ + if (vpd_offset) + break; + + /* We didn't find a VPD entry, go to next ROM entry. */ + base_offset += image_length; + + byte1 = readb(brd->re_map_membase + base_offset); + byte2 = readb(brd->re_map_membase + base_offset + 1); + + /* + * If the new ROM offset doesn't have 0x55, 0xAA + * as its header, we have run out of ROM. + */ + if (byte1 != 0x55 || byte2 != 0xAA) + break; + } + + /* + * If we have a VPD offset, then mark the board + * as having a valid VPD, and copy VPDSIZE (512) bytes of + * that VPD to the buffer we have in our board structure. + */ + if (vpd_offset) { + brd->bd_flags |= BD_HAS_VPD; + for (i = 0; i < VPDSIZE; i++) + brd->vpd[i] = readb(brd->re_map_membase + vpd_offset + i); + } + } + + /* + * We MUST poke the magic number at the PCI Rom Address location again. + * This makes the card report the regular board memory back to us, + * rather than the OTPROM memory. + */ + magic = FEP5_ROM_MAGIC; + pci_write_config_dword(brd->pdev, PCI_ROM_ADDRESS, magic); +} + + +/* + * Our board poller function. + */ +void dgap_poll_tasklet(unsigned long data) +{ + struct board_t *bd = (struct board_t *) data; + ulong lock_flags; + ulong lock_flags2; + char *vaddr; + u16 head, tail; + u16 *chk_addr; + u16 check = 0; + + if (!bd || (bd->magic != DGAP_BOARD_MAGIC)) { + APR(("dgap_poll_tasklet() - NULL or bad bd.\n")); + return; + } + + if (bd->inhibit_poller) + return; + + DGAP_LOCK(bd->bd_lock, lock_flags); + + vaddr = bd->re_map_membase; + + /* + * If board is ready, parse deeper to see if there is anything to do. + */ + if (bd->state == BOARD_READY) { + + struct ev_t *eaddr = NULL; + + if (!bd->re_map_membase) { + DGAP_UNLOCK(bd->bd_lock, lock_flags); + return; + } + if (!bd->re_map_port) { + DGAP_UNLOCK(bd->bd_lock, lock_flags); + return; + } + + if (!bd->nasync) { + goto out; + } + + /* + * If this is a CX or EPCX, we need to see if the firmware + * is requesting a concentrator image from us. + */ + if ((bd->type == PCX) || (bd->type == PEPC)) { + chk_addr = (u16 *) (vaddr + DOWNREQ); + check = readw(chk_addr); + /* Nonzero if FEP is requesting concentrator image. */ + if (check) { + if (bd->conc_dl_status == NO_PENDING_CONCENTRATOR_REQUESTS) + bd->conc_dl_status = NEED_CONCENTRATOR; + /* + * Signal downloader, its got some work to do. + */ + DGAP_LOCK(dgap_dl_lock, lock_flags2); + if (dgap_dl_action != 1) { + dgap_dl_action = 1; + wake_up_interruptible(&dgap_dl_wait); + } + DGAP_UNLOCK(dgap_dl_lock, lock_flags2); + + } + } + + eaddr = (struct ev_t *) (vaddr + EVBUF); + + /* Get our head and tail */ + head = readw(&(eaddr->ev_head)); + tail = readw(&(eaddr->ev_tail)); + + /* + * If there is an event pending. Go service it. + */ + if (head != tail) { + DGAP_UNLOCK(bd->bd_lock, lock_flags); + dgap_event(bd); + DGAP_LOCK(bd->bd_lock, lock_flags); + } + +out: + /* + * If board is doing interrupts, ACK the interrupt. + */ + if (bd && bd->intr_running) { + readb(bd->re_map_port + 2); + } + + DGAP_UNLOCK(bd->bd_lock, lock_flags); + return; + } + + /* Our state machine to get the board up and running */ + + /* Reset board */ + if (bd->state == NEED_RESET) { + + /* Get VPD info */ + dgap_get_vpd(bd); + + dgap_do_reset_board(bd); + } + + /* Move to next state */ + if (bd->state == FINISHED_RESET) { + bd->state = NEED_CONFIG; + } + + if (bd->state == NEED_CONFIG) { + /* + * Match this board to a config the user created for us. + */ + bd->bd_config = dgap_find_config(bd->type, bd->pci_bus, bd->pci_slot); + + /* + * Because the 4 port Xr products share the same PCI ID + * as the 8 port Xr products, if we receive a NULL config + * back, and this is a PAPORT8 board, retry with a + * PAPORT4 attempt as well. + */ + if (bd->type == PAPORT8 && !bd->bd_config) { + bd->bd_config = dgap_find_config(PAPORT4, bd->pci_bus, bd->pci_slot); + } + + /* + * Register the ttys (if any) into the kernel. + */ + if (bd->bd_config) { + bd->state = FINISHED_CONFIG; + } + else { + bd->state = CONFIG_NOT_FOUND; + } + } + + /* Move to next state */ + if (bd->state == FINISHED_CONFIG) { + bd->state = NEED_DEVICE_CREATION; + } + + /* Move to next state */ + if (bd->state == NEED_DEVICE_CREATION) { + /* + * Signal downloader, its got some work to do. + */ + DGAP_LOCK(dgap_dl_lock, lock_flags2); + if (dgap_dl_action != 1) { + dgap_dl_action = 1; + wake_up_interruptible(&dgap_dl_wait); + } + DGAP_UNLOCK(dgap_dl_lock, lock_flags2); + } + + /* Move to next state */ + if (bd->state == FINISHED_DEVICE_CREATION) { + bd->state = NEED_BIOS_LOAD; + } + + /* Move to next state */ + if (bd->state == NEED_BIOS_LOAD) { + /* + * Signal downloader, its got some work to do. + */ + DGAP_LOCK(dgap_dl_lock, lock_flags2); + if (dgap_dl_action != 1) { + dgap_dl_action = 1; + wake_up_interruptible(&dgap_dl_wait); + } + DGAP_UNLOCK(dgap_dl_lock, lock_flags2); + } + + /* Wait for BIOS to test board... */ + if (bd->state == WAIT_BIOS_LOAD) { + dgap_do_wait_for_bios(bd); + } + + /* Move to next state */ + if (bd->state == FINISHED_BIOS_LOAD) { + bd->state = NEED_FEP_LOAD; + + /* + * Signal downloader, its got some work to do. + */ + DGAP_LOCK(dgap_dl_lock, lock_flags2); + if (dgap_dl_action != 1) { + dgap_dl_action = 1; + wake_up_interruptible(&dgap_dl_wait); + } + DGAP_UNLOCK(dgap_dl_lock, lock_flags2); + } + + /* Wait for FEP to load on board... */ + if (bd->state == WAIT_FEP_LOAD) { + dgap_do_wait_for_fep(bd); + } + + + /* Move to next state */ + if (bd->state == FINISHED_FEP_LOAD) { + + /* + * Do tty device initialization. + */ + int rc = dgap_tty_init(bd); + + if (rc < 0) { + dgap_tty_uninit(bd); + APR(("Can't init tty devices (%d)\n", rc)); + bd->state = BOARD_FAILED; + bd->dpastatus = BD_NOFEP; + } + else { + bd->state = NEED_PROC_CREATION; + + /* + * Signal downloader, its got some work to do. + */ + DGAP_LOCK(dgap_dl_lock, lock_flags2); + if (dgap_dl_action != 1) { + dgap_dl_action = 1; + wake_up_interruptible(&dgap_dl_wait); + } + DGAP_UNLOCK(dgap_dl_lock, lock_flags2); + } + } + + /* Move to next state */ + if (bd->state == FINISHED_PROC_CREATION) { + + bd->state = BOARD_READY; + bd->dpastatus = BD_RUNNING; + + /* + * If user requested the board to run in interrupt mode, + * go and set it up on the board. + */ + if (bd->intr_used) { + writew(1, (bd->re_map_membase + ENABLE_INTR)); + /* + * Tell the board to poll the UARTS as fast as possible. + */ + writew(FEPPOLL_MIN, (bd->re_map_membase + FEPPOLL)); + bd->intr_running = 1; + } + + /* Wake up anyone waiting for board state to change to ready */ + wake_up_interruptible(&bd->state_wait); + } + + DGAP_UNLOCK(bd->bd_lock, lock_flags); +} + + +/*======================================================================= + * + * dgap_cmdb - Sends a 2 byte command to the FEP. + * + * ch - Pointer to channel structure. + * cmd - Command to be sent. + * byte1 - Integer containing first byte to be sent. + * byte2 - Integer containing second byte to be sent. + * ncmds - Wait until ncmds or fewer cmds are left + * in the cmd buffer before returning. + * + *=======================================================================*/ +void dgap_cmdb(struct channel_t *ch, uchar cmd, uchar byte1, uchar byte2, uint ncmds) +{ + char *vaddr = NULL; + struct cm_t *cm_addr = NULL; + uint count; + uint n; + u16 head; + u16 tail; + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) + return; + + /* + * Check if board is still alive. + */ + if (ch->ch_bd->state == BOARD_FAILED) { + DPR_CORE(("%s:%d board is in failed state.\n", __FILE__, __LINE__)); + return; + } + + /* + * Make sure the pointers are in range before + * writing to the FEP memory. + */ + vaddr = ch->ch_bd->re_map_membase; + + if (!vaddr) + return; + + cm_addr = (struct cm_t *) (vaddr + CMDBUF); + head = readw(&(cm_addr->cm_head)); + + /* + * Forget it if pointers out of range. + */ + if (head >= (CMDMAX - CMDSTART) || (head & 03)) { + DPR_CORE(("%s:%d pointers out of range, failing board!\n", __FILE__, __LINE__)); + ch->ch_bd->state = BOARD_FAILED; + return; + } + + /* + * Put the data in the circular command buffer. + */ + writeb(cmd, (char *) (vaddr + head + CMDSTART + 0)); + writeb((uchar) ch->ch_portnum, (char *) (vaddr + head + CMDSTART + 1)); + writeb(byte1, (char *) (vaddr + head + CMDSTART + 2)); + writeb(byte2, (char *) (vaddr + head + CMDSTART + 3)); + + head = (head + 4) & (CMDMAX - CMDSTART - 4); + + writew(head, &(cm_addr->cm_head)); + + /* + * Wait if necessary before updating the head + * pointer to limit the number of outstanding + * commands to the FEP. If the time spent waiting + * is outlandish, declare the FEP dead. + */ + for (count = dgap_count ;;) { + + head = readw(&(cm_addr->cm_head)); + tail = readw(&(cm_addr->cm_tail)); + + n = (head - tail) & (CMDMAX - CMDSTART - 4); + + if (n <= ncmds * sizeof(struct cm_t)) + break; + + if (--count == 0) { + DPR_CORE(("%s:%d failing board.\n",__FILE__, __LINE__)); + ch->ch_bd->state = BOARD_FAILED; + return; + } + udelay(10); + } +} + + +/*======================================================================= + * + * dgap_cmdw - Sends a 1 word command to the FEP. + * + * ch - Pointer to channel structure. + * cmd - Command to be sent. + * word - Integer containing word to be sent. + * ncmds - Wait until ncmds or fewer cmds are left + * in the cmd buffer before returning. + * + *=======================================================================*/ +void dgap_cmdw(struct channel_t *ch, uchar cmd, u16 word, uint ncmds) +{ + char *vaddr = NULL; + struct cm_t *cm_addr = NULL; + uint count; + uint n; + u16 head; + u16 tail; + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) + return; + + /* + * Check if board is still alive. + */ + if (ch->ch_bd->state == BOARD_FAILED) { + DPR_CORE(("%s:%d board is failed!\n", __FILE__, __LINE__)); + return; + } + + /* + * Make sure the pointers are in range before + * writing to the FEP memory. + */ + vaddr = ch->ch_bd->re_map_membase; + if (!vaddr) + return; + + cm_addr = (struct cm_t *) (vaddr + CMDBUF); + head = readw(&(cm_addr->cm_head)); + + /* + * Forget it if pointers out of range. + */ + if (head >= (CMDMAX - CMDSTART) || (head & 03)) { + DPR_CORE(("%s:%d Pointers out of range. Failing board.\n",__FILE__, __LINE__)); + ch->ch_bd->state = BOARD_FAILED; + return; + } + + /* + * Put the data in the circular command buffer. + */ + writeb(cmd, (char *) (vaddr + head + CMDSTART + 0)); + writeb((uchar) ch->ch_portnum, (char *) (vaddr + head + CMDSTART + 1)); + writew((u16) word, (char *) (vaddr + head + CMDSTART + 2)); + + head = (head + 4) & (CMDMAX - CMDSTART - 4); + + writew(head, &(cm_addr->cm_head)); + + /* + * Wait if necessary before updating the head + * pointer to limit the number of outstanding + * commands to the FEP. If the time spent waiting + * is outlandish, declare the FEP dead. + */ + for (count = dgap_count ;;) { + + head = readw(&(cm_addr->cm_head)); + tail = readw(&(cm_addr->cm_tail)); + + n = (head - tail) & (CMDMAX - CMDSTART - 4); + + if (n <= ncmds * sizeof(struct cm_t)) + break; + + if (--count == 0) { + DPR_CORE(("%s:%d Failing board.\n",__FILE__, __LINE__)); + ch->ch_bd->state = BOARD_FAILED; + return; + } + udelay(10); + } +} + + + +/*======================================================================= + * + * dgap_cmdw_ext - Sends a extended word command to the FEP. + * + * ch - Pointer to channel structure. + * cmd - Command to be sent. + * word - Integer containing word to be sent. + * ncmds - Wait until ncmds or fewer cmds are left + * in the cmd buffer before returning. + * + *=======================================================================*/ +static void dgap_cmdw_ext(struct channel_t *ch, u16 cmd, u16 word, uint ncmds) +{ + char *vaddr = NULL; + struct cm_t *cm_addr = NULL; + uint count; + uint n; + u16 head; + u16 tail; + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) + return; + + /* + * Check if board is still alive. + */ + if (ch->ch_bd->state == BOARD_FAILED) { + DPR_CORE(("%s:%d board is failed!\n", __FILE__, __LINE__)); + return; + } + + /* + * Make sure the pointers are in range before + * writing to the FEP memory. + */ + vaddr = ch->ch_bd->re_map_membase; + if (!vaddr) + return; + + cm_addr = (struct cm_t *) (vaddr + CMDBUF); + head = readw(&(cm_addr->cm_head)); + + /* + * Forget it if pointers out of range. + */ + if (head >= (CMDMAX - CMDSTART) || (head & 03)) { + DPR_CORE(("%s:%d Pointers out of range. Failing board.\n",__FILE__, __LINE__)); + ch->ch_bd->state = BOARD_FAILED; + return; + } + + /* + * Put the data in the circular command buffer. + */ + + /* Write an FF to tell the FEP that we want an extended command */ + writeb((uchar) 0xff, (char *) (vaddr + head + CMDSTART + 0)); + + writeb((uchar) ch->ch_portnum, (uchar *) (vaddr + head + CMDSTART + 1)); + writew((u16) cmd, (char *) (vaddr + head + CMDSTART + 2)); + + /* + * If the second part of the command won't fit, + * put it at the beginning of the circular buffer. + */ + if (((head + 4) >= ((CMDMAX - CMDSTART)) || (head & 03))) { + writew((u16) word, (char *) (vaddr + CMDSTART)); + } else { + writew((u16) word, (char *) (vaddr + head + CMDSTART + 4)); + } + + head = (head + 8) & (CMDMAX - CMDSTART - 4); + + writew(head, &(cm_addr->cm_head)); + + /* + * Wait if necessary before updating the head + * pointer to limit the number of outstanding + * commands to the FEP. If the time spent waiting + * is outlandish, declare the FEP dead. + */ + for (count = dgap_count ;;) { + + head = readw(&(cm_addr->cm_head)); + tail = readw(&(cm_addr->cm_tail)); + + n = (head - tail) & (CMDMAX - CMDSTART - 4); + + if (n <= ncmds * sizeof(struct cm_t)) + break; + + if (--count == 0) { + DPR_CORE(("%s:%d Failing board.\n",__FILE__, __LINE__)); + ch->ch_bd->state = BOARD_FAILED; + return; + } + udelay(10); + } +} + + +/*======================================================================= + * + * dgap_wmove - Write data to FEP buffer. + * + * ch - Pointer to channel structure. + * buf - Poiter to characters to be moved. + * cnt - Number of characters to move. + * + *=======================================================================*/ +void dgap_wmove(struct channel_t *ch, char *buf, uint cnt) +{ + int n; + char *taddr; + struct bs_t *bs; + u16 head; + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) + return; + + /* + * Check parameters. + */ + bs = ch->ch_bs; + head = readw(&(bs->tx_head)); + + /* + * If pointers are out of range, just return. + */ + if ((cnt > ch->ch_tsize) || (unsigned)(head - ch->ch_tstart) >= ch->ch_tsize) { + DPR_CORE(("%s:%d pointer out of range", __FILE__, __LINE__)); + return; + } + + /* + * If the write wraps over the top of the circular buffer, + * move the portion up to the wrap point, and reset the + * pointers to the bottom. + */ + n = ch->ch_tstart + ch->ch_tsize - head; + + if (cnt >= n) { + cnt -= n; + taddr = ch->ch_taddr + head; + memcpy_toio(taddr, buf, n); + head = ch->ch_tstart; + buf += n; + } + + /* + * Move rest of data. + */ + taddr = ch->ch_taddr + head; + n = cnt; + memcpy_toio(taddr, buf, n); + head += cnt; + + writew(head, &(bs->tx_head)); +} + +/* + * Retrives the current custom baud rate from FEP memory, + * and returns it back to the user. + * Returns 0 on error. + */ +uint dgap_get_custom_baud(struct channel_t *ch) +{ + uchar *vaddr; + ulong offset = 0; + uint value = 0; + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) { + return (0); + } + + if (!ch->ch_bd || ch->ch_bd->magic != DGAP_BOARD_MAGIC) { + return (0); + } + + if (!(ch->ch_bd->bd_flags & BD_FEP5PLUS)) + return (0); + + vaddr = ch->ch_bd->re_map_membase; + + if (!vaddr) + return (0); + + /* + * Go get from fep mem, what the fep + * believes the custom baud rate is. + */ + offset = ((((*(unsigned short *)(vaddr + ECS_SEG)) << 4) + + (ch->ch_portnum * 0x28) + LINE_SPEED)); + + value = readw(vaddr + offset); + return (value); +} + + +/* + * Calls the firmware to reset this channel. + */ +void dgap_firmware_reset_port(struct channel_t *ch) +{ + dgap_cmdb(ch, CHRESET, 0, 0, 0); + + /* + * Now that the channel is reset, we need to make sure + * all the current settings get reapplied to the port + * in the firmware. + * + * So we will set the driver's cache of firmware + * settings all to 0, and then call param. + */ + ch->ch_fepiflag = 0; + ch->ch_fepcflag = 0; + ch->ch_fepoflag = 0; + ch->ch_fepstartc = 0; + ch->ch_fepstopc = 0; + ch->ch_fepastartc = 0; + ch->ch_fepastopc = 0; + ch->ch_mostat = 0; + ch->ch_hflow = 0; +} + + +/*======================================================================= + * + * dgap_param - Set Digi parameters. + * + * struct tty_struct * - TTY for port. + * + *=======================================================================*/ +int dgap_param(struct tty_struct *tty) +{ + struct ktermios *ts; + struct board_t *bd; + struct channel_t *ch; + struct bs_t *bs; + struct un_t *un; + u16 head; + u16 cflag; + u16 iflag; + uchar mval; + uchar hflow; + + if (!tty || tty->magic != TTY_MAGIC) { + return (-ENXIO); + } + + un = (struct un_t *) tty->driver_data; + if (!un || un->magic != DGAP_UNIT_MAGIC) { + return (-ENXIO); + } + + ch = un->un_ch; + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) { + return (-ENXIO); + } + + bd = ch->ch_bd; + if (!bd || bd->magic != DGAP_BOARD_MAGIC) { + return (-ENXIO); + } + + bs = ch->ch_bs; + if (bs == 0) { + return (-ENXIO); + } + + DPR_PARAM(("param start: tdev: %x cflags: %x oflags: %x iflags: %x\n", + ch->ch_tun.un_dev, ch->ch_c_cflag, ch->ch_c_oflag, ch->ch_c_iflag)); + + ts = &tty->termios; + + /* + * If baud rate is zero, flush queues, and set mval to drop DTR. + */ + if ((ch->ch_c_cflag & (CBAUD)) == 0) { + + /* flush rx */ + head = readw(&(ch->ch_bs->rx_head)); + writew(head, &(ch->ch_bs->rx_tail)); + + /* flush tx */ + head = readw(&(ch->ch_bs->tx_head)); + writew(head, &(ch->ch_bs->tx_tail)); + + ch->ch_flags |= (CH_BAUD0); + + /* Drop RTS and DTR */ + ch->ch_mval &= ~(D_RTS(ch)|D_DTR(ch)); + mval = D_DTR(ch) | D_RTS(ch); + ch->ch_baud_info = 0; + + } else if (ch->ch_custom_speed && (bd->bd_flags & BD_FEP5PLUS)) { + /* + * Tell the fep to do the command + */ + + DPR_PARAM(("param: Want %d speed\n", ch->ch_custom_speed)); + + dgap_cmdw_ext(ch, 0xff01, ch->ch_custom_speed, 0); + + /* + * Now go get from fep mem, what the fep + * believes the custom baud rate is. + */ + ch->ch_baud_info = ch->ch_custom_speed = dgap_get_custom_baud(ch); + + DPR_PARAM(("param: Got %d speed\n", ch->ch_custom_speed)); + + /* Handle transition from B0 */ + if (ch->ch_flags & CH_BAUD0) { + ch->ch_flags &= ~(CH_BAUD0); + ch->ch_mval |= (D_RTS(ch)|D_DTR(ch)); + } + mval = D_DTR(ch) | D_RTS(ch); + + } else { + /* + * Set baud rate, character size, and parity. + */ + + + int iindex = 0; + int jindex = 0; + int baud = 0; + + ulong bauds[4][16] = { + { /* slowbaud */ + 0, 50, 75, 110, + 134, 150, 200, 300, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* slowbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* fastbaud */ + 0, 57600, 76800, 115200, + 14400, 57600, 230400, 76800, + 115200, 230400, 28800, 460800, + 921600, 9600, 19200, 38400 }, + { /* fastbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 } + }; + + /* Only use the TXPrint baud rate if the terminal unit is NOT open */ + if (!(ch->ch_tun.un_flags & UN_ISOPEN) && (un->un_type == DGAP_PRINT)) + baud = C_BAUD(ch->ch_pun.un_tty) & 0xff; + else + baud = C_BAUD(ch->ch_tun.un_tty) & 0xff; + + if (ch->ch_c_cflag & CBAUDEX) + iindex = 1; + + if (ch->ch_digi.digi_flags & DIGI_FAST) + iindex += 2; + + jindex = baud; + + if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) && (jindex < 16)) { + baud = bauds[iindex][jindex]; + } else { + DPR_IOCTL(("baud indices were out of range (%d)(%d)", + iindex, jindex)); + baud = 0; + } + + if (baud == 0) + baud = 9600; + + ch->ch_baud_info = baud; + + + /* + * CBAUD has bit position 0x1000 set these days to indicate Linux + * baud rate remap. + * We use a different bit assignment for high speed. Clear this + * bit out while grabbing the parts of "cflag" we want. + */ + cflag = ch->ch_c_cflag & ((CBAUD ^ CBAUDEX) | PARODD | PARENB | CSTOPB | CSIZE); + + /* + * HUPCL bit is used by FEP to indicate fast baud + * table is to be used. + */ + if ((ch->ch_digi.digi_flags & DIGI_FAST) || (ch->ch_c_cflag & CBAUDEX)) + cflag |= HUPCL; + + + if ((ch->ch_c_cflag & CBAUDEX) && !(ch->ch_digi.digi_flags & DIGI_FAST)) { + /* + * The below code is trying to guarantee that only baud rates + * 115200, 230400, 460800, 921600 are remapped. We use exclusive or + * because the various baud rates share common bit positions + * and therefore can't be tested for easily. + */ + tcflag_t tcflag = (ch->ch_c_cflag & CBAUD) | CBAUDEX; + int baudpart = 0; + + /* Map high speed requests to index into FEP's baud table */ + switch (tcflag) { + case B57600 : + baudpart = 1; + break; +#ifdef B76800 + case B76800 : + baudpart = 2; + break; +#endif + case B115200 : + baudpart = 3; + break; + case B230400 : + baudpart = 9; + break; + case B460800 : + baudpart = 11; + break; +#ifdef B921600 + case B921600 : + baudpart = 12; + break; +#endif + default: + baudpart = 0; + } + + if (baudpart) + cflag = (cflag & ~(CBAUD | CBAUDEX)) | baudpart; + } + + cflag &= 0xffff; + + if (cflag != ch->ch_fepcflag) { + ch->ch_fepcflag = (u16) (cflag & 0xffff); + + /* Okay to have channel and board locks held calling this */ + dgap_cmdw(ch, SCFLAG, (u16) cflag, 0); + } + + /* Handle transition from B0 */ + if (ch->ch_flags & CH_BAUD0) { + ch->ch_flags &= ~(CH_BAUD0); + ch->ch_mval |= (D_RTS(ch)|D_DTR(ch)); + } + mval = D_DTR(ch) | D_RTS(ch); + } + + /* + * Get input flags. + */ + iflag = ch->ch_c_iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | IXON | IXANY | IXOFF); + + if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE)) { + iflag &= ~(IXON | IXOFF); + ch->ch_c_iflag &= ~(IXON | IXOFF); + } + + /* + * Only the IBM Xr card can switch between + * 232 and 422 modes on the fly + */ + if (bd->device == PCI_DEVICE_XR_IBM_DID) { + if (ch->ch_digi.digi_flags & DIGI_422) + dgap_cmdb(ch, SCOMMODE, MODE_422, 0, 0); + else + dgap_cmdb(ch, SCOMMODE, MODE_232, 0, 0); + } + + if (ch->ch_digi.digi_flags & DIGI_ALTPIN) + iflag |= IALTPIN ; + + if (iflag != ch->ch_fepiflag) { + ch->ch_fepiflag = iflag; + + /* Okay to have channel and board locks held calling this */ + dgap_cmdw(ch, SIFLAG, (u16) ch->ch_fepiflag, 0); + } + + /* + * Select hardware handshaking. + */ + hflow = 0; + + if (ch->ch_c_cflag & CRTSCTS) { + hflow |= (D_RTS(ch) | D_CTS(ch)); + } + if (ch->ch_digi.digi_flags & RTSPACE) + hflow |= D_RTS(ch); + if (ch->ch_digi.digi_flags & DTRPACE) + hflow |= D_DTR(ch); + if (ch->ch_digi.digi_flags & CTSPACE) + hflow |= D_CTS(ch); + if (ch->ch_digi.digi_flags & DSRPACE) + hflow |= D_DSR(ch); + if (ch->ch_digi.digi_flags & DCDPACE) + hflow |= D_CD(ch); + + if (hflow != ch->ch_hflow) { + ch->ch_hflow = hflow; + + /* Okay to have channel and board locks held calling this */ + dgap_cmdb(ch, SHFLOW, (uchar) hflow, 0xff, 0); + } + + + /* + * Set RTS and/or DTR Toggle if needed, but only if product is FEP5+ based. + */ + if (bd->bd_flags & BD_FEP5PLUS) { + u16 hflow2 = 0; + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) { + hflow2 |= (D_RTS(ch)); + } + if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) { + hflow2 |= (D_DTR(ch)); + } + + dgap_cmdw_ext(ch, 0xff03, hflow2, 0); + } + + /* + * Set modem control lines. + */ + + mval ^= ch->ch_mforce & (mval ^ ch->ch_mval); + + DPR_PARAM(("dgap_param: mval: %x ch_mforce: %x ch_mval: %x ch_mostat: %x\n", + mval, ch->ch_mforce, ch->ch_mval, ch->ch_mostat)); + + if (ch->ch_mostat ^ mval) { + ch->ch_mostat = mval; + + /* Okay to have channel and board locks held calling this */ + DPR_PARAM(("dgap_param: Sending SMODEM mval: %x\n", mval)); + dgap_cmdb(ch, SMODEM, (uchar) mval, D_RTS(ch)|D_DTR(ch), 0); + } + + /* + * Read modem signals, and then call carrier function. + */ + ch->ch_mistat = readb(&(bs->m_stat)); + dgap_carrier(ch); + + /* + * Set the start and stop characters. + */ + if (ch->ch_startc != ch->ch_fepstartc || ch->ch_stopc != ch->ch_fepstopc) { + ch->ch_fepstartc = ch->ch_startc; + ch->ch_fepstopc = ch->ch_stopc; + + /* Okay to have channel and board locks held calling this */ + dgap_cmdb(ch, SFLOWC, ch->ch_fepstartc, ch->ch_fepstopc, 0); + } + + /* + * Set the Auxiliary start and stop characters. + */ + if (ch->ch_astartc != ch->ch_fepastartc || ch->ch_astopc != ch->ch_fepastopc) { + ch->ch_fepastartc = ch->ch_astartc; + ch->ch_fepastopc = ch->ch_astopc; + + /* Okay to have channel and board locks held calling this */ + dgap_cmdb(ch, SAFLOWC, ch->ch_fepastartc, ch->ch_fepastopc, 0); + } + + DPR_PARAM(("param finish\n")); + + return (0); +} + + +/* + * dgap_parity_scan() + * + * Convert the FEP5 way of reporting parity errors and breaks into + * the Linux line discipline way. + */ +void dgap_parity_scan(struct channel_t *ch, unsigned char *cbuf, unsigned char *fbuf, int *len) +{ + int l = *len; + int count = 0; + unsigned char *in, *cout, *fout; + unsigned char c; + + in = cbuf; + cout = cbuf; + fout = fbuf; + + DPR_PSCAN(("dgap_parity_scan start\n")); + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) + return; + + while (l--) { + c = *in++; + switch (ch->pscan_state) { + default: + /* reset to sanity and fall through */ + ch->pscan_state = 0; + + case 0: + /* No FF seen yet */ + if (c == (unsigned char) '\377') { + /* delete this character from stream */ + ch->pscan_state = 1; + } else { + *cout++ = c; + *fout++ = TTY_NORMAL; + count += 1; + } + break; + + case 1: + /* first FF seen */ + if (c == (unsigned char) '\377') { + /* doubled ff, transform to single ff */ + *cout++ = c; + *fout++ = TTY_NORMAL; + count += 1; + ch->pscan_state = 0; + } else { + /* save value examination in next state */ + ch->pscan_savechar = c; + ch->pscan_state = 2; + } + break; + + case 2: + /* third character of ff sequence */ + + *cout++ = c; + + if (ch->pscan_savechar == 0x0) { + + if (c == 0x0) { + DPR_PSCAN(("dgap_parity_scan in 3rd char of ff seq. c: %x setting break.\n", c)); + ch->ch_err_break++; + *fout++ = TTY_BREAK; + } + else { + DPR_PSCAN(("dgap_parity_scan in 3rd char of ff seq. c: %x setting parity.\n", c)); + ch->ch_err_parity++; + *fout++ = TTY_PARITY; + } + } + else { + DPR_PSCAN(("%s:%d Logic Error.\n", __FILE__, __LINE__)); + } + + count += 1; + ch->pscan_state = 0; + } + } + *len = count; + DPR_PSCAN(("dgap_parity_scan finish\n")); +} + + + + +/*======================================================================= + * + * dgap_event - FEP to host event processing routine. + * + * bd - Board of current event. + * + *=======================================================================*/ +static int dgap_event(struct board_t *bd) +{ + struct channel_t *ch; + ulong lock_flags; + ulong lock_flags2; + struct bs_t *bs; + uchar *event; + uchar *vaddr = NULL; + struct ev_t *eaddr = NULL; + uint head; + uint tail; + int port; + int reason; + int modem; + int b1; + + if (!bd || bd->magic != DGAP_BOARD_MAGIC) + return (-ENXIO); + + DGAP_LOCK(bd->bd_lock, lock_flags); + + vaddr = bd->re_map_membase; + + if (!vaddr) { + DGAP_UNLOCK(bd->bd_lock, lock_flags); + return (-ENXIO); + } + + eaddr = (struct ev_t *) (vaddr + EVBUF); + + /* Get our head and tail */ + head = readw(&(eaddr->ev_head)); + tail = readw(&(eaddr->ev_tail)); + + /* + * Forget it if pointers out of range. + */ + + if (head >= EVMAX - EVSTART || tail >= EVMAX - EVSTART || + (head | tail) & 03) { + DPR_EVENT(("should be calling xxfail %d\n", __LINE__)); + /* Let go of board lock */ + DGAP_UNLOCK(bd->bd_lock, lock_flags); + return (-ENXIO); + } + + /* + * Loop to process all the events in the buffer. + */ + while (tail != head) { + + /* + * Get interrupt information. + */ + + event = bd->re_map_membase + tail + EVSTART; + + port = event[0]; + reason = event[1]; + modem = event[2]; + b1 = event[3]; + + DPR_EVENT(("event: jiffies: %ld port: %d reason: %x modem: %x\n", + jiffies, port, reason, modem)); + + /* + * Make sure the interrupt is valid. + */ + if ( port >= bd->nasync) { + goto next; + } + + if (!(reason & (IFMODEM | IFBREAK | IFTLW | IFTEM | IFDATA))) { + goto next; + } + + ch = bd->channels[port]; + + if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) { + goto next; + } + + /* + * If we have made it here, the event was valid. + * Lock down the channel. + */ + DGAP_LOCK(ch->ch_lock, lock_flags2); + + bs = ch->ch_bs; + + if (!bs) { + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + goto next; + } + + /* + * Process received data. + */ + if (reason & IFDATA) { + + /* + * ALL LOCKS *MUST* BE DROPPED BEFORE CALLING INPUT! + * input could send some data to ld, which in turn + * could do a callback to one of our other functions. + */ + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + DGAP_UNLOCK(bd->bd_lock, lock_flags); + + dgap_input(ch); + + DGAP_LOCK(bd->bd_lock, lock_flags); + DGAP_LOCK(ch->ch_lock, lock_flags2); + + if (ch->ch_flags & CH_RACTIVE) + ch->ch_flags |= CH_RENABLE; + else + writeb(1, &(bs->idata)); + + if (ch->ch_flags & CH_RWAIT) { + ch->ch_flags &= ~CH_RWAIT; + + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + } + + /* + * Process Modem change signals. + */ + if (reason & IFMODEM) { + ch->ch_mistat = modem; + dgap_carrier(ch); + } + + /* + * Process break. + */ + if (reason & IFBREAK) { + + DPR_EVENT(("got IFBREAK\n")); + + if (ch->ch_tun.un_tty) { + /* A break has been indicated */ + ch->ch_err_break++; + tty_buffer_request_room(ch->ch_tun.un_tty->port, 1); + tty_insert_flip_char(ch->ch_tun.un_tty->port, 0, TTY_BREAK); + tty_flip_buffer_push(ch->ch_tun.un_tty->port); + } + } + + /* + * Process Transmit low. + */ + if (reason & IFTLW) { + + DPR_EVENT(("event: got low event\n")); + + if (ch->ch_tun.un_flags & UN_LOW) { + ch->ch_tun.un_flags &= ~UN_LOW; + + if (ch->ch_tun.un_flags & UN_ISOPEN) { + if ((ch->ch_tun.un_tty->flags & + (1 << TTY_DO_WRITE_WAKEUP)) && +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + ch->ch_tun.un_tty->ldisc->ops->write_wakeup) +#else + ch->ch_tun.un_tty->ldisc.ops->write_wakeup) +#endif + { + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + DGAP_UNLOCK(bd->bd_lock, lock_flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty); +#else + (ch->ch_tun.un_tty->ldisc.ops->write_wakeup)(ch->ch_tun.un_tty); +#endif + DGAP_LOCK(bd->bd_lock, lock_flags); + DGAP_LOCK(ch->ch_lock, lock_flags2); + } + wake_up_interruptible(&ch->ch_tun.un_tty->write_wait); + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + + DPR_EVENT(("event: Got low event. jiffies: %lu\n", jiffies)); + } + } + + if (ch->ch_pun.un_flags & UN_LOW) { + ch->ch_pun.un_flags &= ~UN_LOW; + if (ch->ch_pun.un_flags & UN_ISOPEN) { + if ((ch->ch_pun.un_tty->flags & + (1 << TTY_DO_WRITE_WAKEUP)) && +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + ch->ch_pun.un_tty->ldisc->ops->write_wakeup) +#else + ch->ch_pun.un_tty->ldisc.ops->write_wakeup) +#endif + { + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + DGAP_UNLOCK(bd->bd_lock, lock_flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty); +#else + (ch->ch_pun.un_tty->ldisc.ops->write_wakeup)(ch->ch_pun.un_tty); +#endif + DGAP_LOCK(bd->bd_lock, lock_flags); + DGAP_LOCK(ch->ch_lock, lock_flags2); + } + wake_up_interruptible(&ch->ch_pun.un_tty->write_wait); + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + } + + if (ch->ch_flags & CH_WLOW) { + ch->ch_flags &= ~CH_WLOW; + wake_up_interruptible(&ch->ch_flags_wait); + } + } + + /* + * Process Transmit empty. + */ + if (reason & IFTEM) { + DPR_EVENT(("event: got empty event\n")); + + if (ch->ch_tun.un_flags & UN_EMPTY) { + ch->ch_tun.un_flags &= ~UN_EMPTY; + if (ch->ch_tun.un_flags & UN_ISOPEN) { + if ((ch->ch_tun.un_tty->flags & + (1 << TTY_DO_WRITE_WAKEUP)) && +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + ch->ch_tun.un_tty->ldisc->ops->write_wakeup) +#else + ch->ch_tun.un_tty->ldisc.ops->write_wakeup) +#endif + { + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + DGAP_UNLOCK(bd->bd_lock, lock_flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty); +#else + (ch->ch_tun.un_tty->ldisc.ops->write_wakeup)(ch->ch_tun.un_tty); +#endif + DGAP_LOCK(bd->bd_lock, lock_flags); + DGAP_LOCK(ch->ch_lock, lock_flags2); + } + wake_up_interruptible(&ch->ch_tun.un_tty->write_wait); + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + } + + if (ch->ch_pun.un_flags & UN_EMPTY) { + ch->ch_pun.un_flags &= ~UN_EMPTY; + if (ch->ch_pun.un_flags & UN_ISOPEN) { + if ((ch->ch_pun.un_tty->flags & + (1 << TTY_DO_WRITE_WAKEUP)) && +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + ch->ch_pun.un_tty->ldisc->ops->write_wakeup) +#else + ch->ch_pun.un_tty->ldisc.ops->write_wakeup) +#endif + { + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + DGAP_UNLOCK(bd->bd_lock, lock_flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31) + (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty); +#else + (ch->ch_pun.un_tty->ldisc.ops->write_wakeup)(ch->ch_pun.un_tty); +#endif + DGAP_LOCK(bd->bd_lock, lock_flags); + DGAP_LOCK(ch->ch_lock, lock_flags2); + } + wake_up_interruptible(&ch->ch_pun.un_tty->write_wait); + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + } + + + if (ch->ch_flags & CH_WEMPTY) { + ch->ch_flags &= ~CH_WEMPTY; + wake_up_interruptible(&ch->ch_flags_wait); + } + } + + DGAP_UNLOCK(ch->ch_lock, lock_flags2); + +next: + tail = (tail + 4) & (EVMAX - EVSTART - 4); + } + + writew(tail, &(eaddr->ev_tail)); + DGAP_UNLOCK(bd->bd_lock, lock_flags); + + return (0); +} |