| /* |
| * 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. |
| * |
| */ |
| |
| /* |
| * In the original out of kernel Digi dgap driver, firmware |
| * loading was done via user land to driver handshaking. |
| * |
| * For cards that support a concentrator (port expander), |
| * I believe the concentrator its self told the card which |
| * concentrator is actually attached and then that info |
| * was used to tell user land which concentrator firmware |
| * image was to be downloaded. I think even the BIOS or |
| * FEP images required could change with the connection |
| * of a particular concentrator. |
| * |
| * Since I have no access to any of these cards or |
| * concentrators, I cannot put the correct concentrator |
| * firmware file names into the firmware_info structure |
| * as is now done for the BIOS and FEP images. |
| * |
| * I think, but am not certain, that the cards supporting |
| * concentrators will function without them. So support |
| * of these cards has been left in this driver. |
| * |
| * In order to fully support those cards, they would |
| * either have to be acquired for dissection or maybe |
| * Digi International could provide some assistance. |
| */ |
| #undef DIGI_CONCENTRATORS_SUPPORTED |
| |
| #define pr_fmt(fmt) "dgap: " fmt |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/pci.h> |
| #include <linux/delay.h> /* For udelay */ |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/sched.h> |
| |
| #include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */ |
| #include <linux/ctype.h> |
| #include <linux/tty.h> |
| #include <linux/tty_flip.h> |
| #include <linux/serial_reg.h> |
| #include <linux/io.h> /* For read[bwl]/write[bwl] */ |
| |
| #include <linux/string.h> |
| #include <linux/device.h> |
| #include <linux/kdev_t.h> |
| #include <linux/firmware.h> |
| |
| #include "dgap.h" |
| |
| /* |
| * File operations permitted on Control/Management major. |
| */ |
| static const struct file_operations dgap_board_fops = { |
| .owner = THIS_MODULE, |
| }; |
| |
| static uint dgap_numboards; |
| static struct board_t *dgap_board[MAXBOARDS]; |
| static ulong dgap_poll_counter; |
| static int dgap_driver_state = DRIVER_INITIALIZED; |
| static int dgap_poll_tick = 20; /* Poll interval - 20 ms */ |
| |
| static struct class *dgap_class; |
| |
| static uint dgap_count = 500; |
| |
| /* |
| * Poller stuff |
| */ |
| static DEFINE_SPINLOCK(dgap_poll_lock); /* Poll scheduling lock */ |
| static ulong dgap_poll_time; /* Time of next poll */ |
| static uint dgap_poll_stop; /* Used to tell poller to stop */ |
| static struct timer_list dgap_poll_timer; |
| |
| /* |
| SUPPORTED PRODUCTS |
| |
| Card Model Number of Ports Interface |
| ---------------------------------------------------------------- |
| Acceleport Xem 4 - 64 (EIA232 & EIA422) |
| Acceleport Xr 4 & 8 (EIA232) |
| Acceleport Xr 920 4 & 8 (EIA232) |
| Acceleport C/X 8 - 128 (EIA232) |
| Acceleport EPC/X 8 - 224 (EIA232) |
| Acceleport Xr/422 4 & 8 (EIA422) |
| Acceleport 2r/920 2 (EIA232) |
| Acceleport 4r/920 4 (EIA232) |
| Acceleport 8r/920 8 (EIA232) |
| |
| IBM 8-Port Asynchronous PCI Adapter (EIA232) |
| IBM 128-Port Asynchronous PCI Adapter (EIA232 & EIA422) |
| */ |
| |
| static struct pci_device_id dgap_pci_tbl[] = { |
| { DIGI_VID, PCI_DEV_XEM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, |
| { DIGI_VID, PCI_DEV_CX_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 }, |
| { DIGI_VID, PCI_DEV_CX_IBM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2 }, |
| { DIGI_VID, PCI_DEV_EPCJ_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 3 }, |
| { DIGI_VID, PCI_DEV_920_2_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4 }, |
| { DIGI_VID, PCI_DEV_920_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5 }, |
| { DIGI_VID, PCI_DEV_920_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 6 }, |
| { DIGI_VID, PCI_DEV_XR_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 7 }, |
| { DIGI_VID, PCI_DEV_XRJ_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 8 }, |
| { DIGI_VID, PCI_DEV_XR_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 9 }, |
| { DIGI_VID, PCI_DEV_XR_IBM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 10 }, |
| { DIGI_VID, PCI_DEV_XR_SAIP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 11 }, |
| { DIGI_VID, PCI_DEV_XR_BULL_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 12 }, |
| { DIGI_VID, PCI_DEV_920_8_HP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 13 }, |
| { DIGI_VID, PCI_DEV_XEM_HP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 14 }, |
| {0,} /* 0 terminated list. */ |
| }; |
| MODULE_DEVICE_TABLE(pci, dgap_pci_tbl); |
| |
| /* |
| * A generic list of Product names, PCI Vendor ID, and PCI Device ID. |
| */ |
| struct board_id { |
| uint config_type; |
| u8 *name; |
| uint maxports; |
| uint dpatype; |
| }; |
| |
| static struct board_id dgap_ids[] = { |
| { PPCM, PCI_DEV_XEM_NAME, 64, (T_PCXM|T_PCLITE|T_PCIBUS) }, |
| { PCX, PCI_DEV_CX_NAME, 128, (T_CX|T_PCIBUS) }, |
| { PCX, PCI_DEV_CX_IBM_NAME, 128, (T_CX|T_PCIBUS) }, |
| { PEPC, PCI_DEV_EPCJ_NAME, 224, (T_EPC|T_PCIBUS) }, |
| { APORT2_920P, PCI_DEV_920_2_NAME, 2, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { APORT4_920P, PCI_DEV_920_4_NAME, 4, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { APORT8_920P, PCI_DEV_920_8_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PAPORT8, PCI_DEV_XR_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PAPORT8, PCI_DEV_XRJ_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PAPORT8, PCI_DEV_XR_422_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PAPORT8, PCI_DEV_XR_IBM_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PAPORT8, PCI_DEV_XR_SAIP_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PAPORT8, PCI_DEV_XR_BULL_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { APORT8_920P, PCI_DEV_920_8_HP_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) }, |
| { PPCM, PCI_DEV_XEM_HP_NAME, 64, (T_PCXM|T_PCLITE|T_PCIBUS) }, |
| {0,} /* 0 terminated list. */ |
| }; |
| |
| struct firmware_info { |
| u8 *conf_name; /* dgap.conf */ |
| u8 *bios_name; /* BIOS filename */ |
| u8 *fep_name; /* FEP filename */ |
| u8 *con_name; /* Concentrator filename FIXME*/ |
| int num; /* sequence number */ |
| }; |
| |
| /* |
| * Firmware - BIOS, FEP, and CONC filenames |
| */ |
| static struct firmware_info fw_info[] = { |
| { "dgap/dgap.conf", "dgap/sxbios.bin", "dgap/sxfep.bin", NULL, 0 }, |
| { "dgap/dgap.conf", "dgap/cxpbios.bin", "dgap/cxpfep.bin", NULL, 1 }, |
| { "dgap/dgap.conf", "dgap/cxpbios.bin", "dgap/cxpfep.bin", NULL, 2 }, |
| { "dgap/dgap.conf", "dgap/pcibios.bin", "dgap/pcifep.bin", NULL, 3 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 4 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 5 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 6 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 7 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 8 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 9 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 10 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 11 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 12 }, |
| { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 13 }, |
| { "dgap/dgap.conf", "dgap/sxbios.bin", "dgap/sxfep.bin", NULL, 14 }, |
| {NULL,} |
| }; |
| |
| /* |
| * Default transparent print information. |
| */ |
| static struct digi_t dgap_digi_init = { |
| .digi_flags = DIGI_COOK, /* Flags */ |
| .digi_maxcps = 100, /* Max CPS */ |
| .digi_maxchar = 50, /* Max chars in print queue */ |
| .digi_bufsize = 100, /* Printer buffer size */ |
| .digi_onlen = 4, /* size of printer on string */ |
| .digi_offlen = 4, /* size of printer off string */ |
| .digi_onstr = "\033[5i", /* ANSI printer on string ] */ |
| .digi_offstr = "\033[4i", /* ANSI printer off string ] */ |
| .digi_term = "ansi" /* default terminal type */ |
| }; |
| |
| /* |
| * Define a local default termios struct. All ports will be created |
| * with this termios initially. |
| * |
| * This defines a raw port at 9600 baud, 8 data bits, no parity, |
| * 1 stop bit. |
| */ |
| |
| static struct ktermios dgap_default_termios = { |
| .c_iflag = (DEFAULT_IFLAGS), /* iflags */ |
| .c_oflag = (DEFAULT_OFLAGS), /* oflags */ |
| .c_cflag = (DEFAULT_CFLAGS), /* cflags */ |
| .c_lflag = (DEFAULT_LFLAGS), /* lflags */ |
| .c_cc = INIT_C_CC, |
| .c_line = 0, |
| }; |
| |
| /* |
| * Our needed internal static variables from dgap_parse.c |
| */ |
| static struct cnode dgap_head; |
| #define MAXCWORD 200 |
| static char dgap_cword[MAXCWORD]; |
| |
| struct toklist { |
| int token; |
| char *string; |
| }; |
| |
| static struct toklist dgap_brdtype[] = { |
| { PCX, "Digi_AccelePort_C/X_PCI" }, |
| { PEPC, "Digi_AccelePort_EPC/X_PCI" }, |
| { PPCM, "Digi_AccelePort_Xem_PCI" }, |
| { APORT2_920P, "Digi_AccelePort_2r_920_PCI" }, |
| { APORT4_920P, "Digi_AccelePort_4r_920_PCI" }, |
| { APORT8_920P, "Digi_AccelePort_8r_920_PCI" }, |
| { PAPORT4, "Digi_AccelePort_4r_PCI(EIA-232/RS-422)" }, |
| { PAPORT8, "Digi_AccelePort_8r_PCI(EIA-232/RS-422)" }, |
| { 0, NULL } |
| }; |
| |
| static struct toklist dgap_tlist[] = { |
| { BEGIN, "config_begin" }, |
| { END, "config_end" }, |
| { BOARD, "board" }, |
| { IO, "io" }, |
| { PCIINFO, "pciinfo" }, |
| { LINE, "line" }, |
| { CONC, "conc" }, |
| { CONC, "concentrator" }, |
| { CX, "cx" }, |
| { CX, "ccon" }, |
| { EPC, "epccon" }, |
| { EPC, "epc" }, |
| { MOD, "module" }, |
| { ID, "id" }, |
| { STARTO, "start" }, |
| { SPEED, "speed" }, |
| { CABLE, "cable" }, |
| { CONNECT, "connect" }, |
| { METHOD, "method" }, |
| { STATUS, "status" }, |
| { CUSTOM, "Custom" }, |
| { BASIC, "Basic" }, |
| { MEM, "mem" }, |
| { MEM, "memory" }, |
| { PORTS, "ports" }, |
| { MODEM, "modem" }, |
| { NPORTS, "nports" }, |
| { TTYN, "ttyname" }, |
| { CU, "cuname" }, |
| { PRINT, "prname" }, |
| { CMAJOR, "major" }, |
| { ALTPIN, "altpin" }, |
| { USEINTR, "useintr" }, |
| { TTSIZ, "ttysize" }, |
| { CHSIZ, "chsize" }, |
| { BSSIZ, "boardsize" }, |
| { UNTSIZ, "schedsize" }, |
| { F2SIZ, "f2200size" }, |
| { VPSIZ, "vpixsize" }, |
| { 0, NULL } |
| }; |
| |
| |
| /* |
| * dgap_sindex: much like index(), but it looks for a match of any character in |
| * the group, and returns that position. |
| */ |
| static char *dgap_sindex(char *string, char *group) |
| { |
| char *ptr; |
| |
| if (!string || !group) |
| return NULL; |
| |
| for (; *string; string++) { |
| for (ptr = group; *ptr; ptr++) { |
| if (*ptr == *string) |
| return string; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * get a word from the input stream, also keep track of current line number. |
| * words are separated by whitespace. |
| */ |
| static char *dgap_getword(char **in) |
| { |
| char *ret_ptr = *in; |
| |
| char *ptr = dgap_sindex(*in, " \t\n"); |
| |
| /* If no word found, return null */ |
| if (!ptr) |
| return NULL; |
| |
| /* Mark new location for our buffer */ |
| *ptr = '\0'; |
| *in = ptr + 1; |
| |
| /* Eat any extra spaces/tabs/newlines that might be present */ |
| while (*in && **in && ((**in == ' ') || |
| (**in == '\t') || |
| (**in == '\n'))) { |
| **in = '\0'; |
| *in = *in + 1; |
| } |
| |
| return ret_ptr; |
| } |
| |
| |
| /* |
| * Get a token from the input file; return 0 if end of file is reached |
| */ |
| static int dgap_gettok(char **in) |
| { |
| char *w; |
| struct toklist *t; |
| |
| if (strstr(dgap_cword, "board")) { |
| w = dgap_getword(in); |
| snprintf(dgap_cword, MAXCWORD, "%s", w); |
| for (t = dgap_brdtype; t->token != 0; t++) { |
| if (!strcmp(w, t->string)) |
| return t->token; |
| } |
| } else { |
| while ((w = dgap_getword(in))) { |
| snprintf(dgap_cword, MAXCWORD, "%s", w); |
| for (t = dgap_tlist; t->token != 0; t++) { |
| if (!strcmp(w, t->string)) |
| return t->token; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_checknode: see if all the necessary info has been supplied for a node |
| * before creating the next node. |
| */ |
| static int dgap_checknode(struct cnode *p) |
| { |
| switch (p->type) { |
| case LNODE: |
| if (p->u.line.v_speed == 0) { |
| pr_err("line speed not specified"); |
| return 1; |
| } |
| return 0; |
| |
| case CNODE: |
| if (p->u.conc.v_speed == 0) { |
| pr_err("concentrator line speed not specified"); |
| return 1; |
| } |
| if (p->u.conc.v_nport == 0) { |
| pr_err("number of ports on concentrator not specified"); |
| return 1; |
| } |
| if (p->u.conc.v_id == 0) { |
| pr_err("concentrator id letter not specified"); |
| return 1; |
| } |
| return 0; |
| |
| case MNODE: |
| if (p->u.module.v_nport == 0) { |
| pr_err("number of ports on EBI module not specified"); |
| return 1; |
| } |
| if (p->u.module.v_id == 0) { |
| pr_err("EBI module id letter not specified"); |
| return 1; |
| } |
| return 0; |
| } |
| return 0; |
| } |
| |
| /* |
| * Given a board pointer, returns whether we should use interrupts or not. |
| */ |
| static uint dgap_config_get_useintr(struct board_t *bd) |
| { |
| struct cnode *p; |
| |
| if (!bd) |
| return 0; |
| |
| for (p = bd->bd_config; p; p = p->next) { |
| if (p->type == INTRNODE) { |
| /* |
| * check for pcxr types. |
| */ |
| return p->u.useintr; |
| } |
| } |
| |
| /* If not found, then don't turn on interrupts. */ |
| return 0; |
| } |
| |
| /* |
| * Given a board pointer, returns whether we turn on altpin or not. |
| */ |
| static uint dgap_config_get_altpin(struct board_t *bd) |
| { |
| struct cnode *p; |
| |
| if (!bd) |
| return 0; |
| |
| for (p = bd->bd_config; p; p = p->next) { |
| if (p->type == ANODE) { |
| /* |
| * check for pcxr types. |
| */ |
| return p->u.altpin; |
| } |
| } |
| |
| /* If not found, then don't turn on interrupts. */ |
| return 0; |
| } |
| |
| /* |
| * Given a specific type of board, if found, detached link and |
| * returns the first occurrence in the list. |
| */ |
| static struct cnode *dgap_find_config(int type, int bus, int slot) |
| { |
| struct cnode *p, *prev, *prev2, *found; |
| |
| p = &dgap_head; |
| |
| while (p->next) { |
| prev = p; |
| p = p->next; |
| |
| if (p->type != BNODE) |
| continue; |
| |
| if (p->u.board.type != type) |
| continue; |
| |
| if (p->u.board.v_pcibus && |
| p->u.board.pcibus != bus) |
| continue; |
| |
| if (p->u.board.v_pcislot && |
| p->u.board.pcislot != slot) |
| continue; |
| |
| found = p; |
| /* |
| * Keep walking thru the list till we |
| * find the next board. |
| */ |
| while (p->next) { |
| prev2 = p; |
| p = p->next; |
| |
| if (p->type != BNODE) |
| continue; |
| |
| /* |
| * Mark the end of our 1 board |
| * chain of configs. |
| */ |
| prev2->next = NULL; |
| |
| /* |
| * Link the "next" board to the |
| * previous board, effectively |
| * "unlinking" our board from |
| * the main config. |
| */ |
| prev->next = p; |
| |
| return found; |
| } |
| /* |
| * It must be the last board in the list. |
| */ |
| prev->next = NULL; |
| return found; |
| } |
| return NULL; |
| } |
| |
| /* |
| * Given a board pointer, walks the config link, counting up |
| * all ports user specified should be on the board. |
| * (This does NOT mean they are all actually present right now tho) |
| */ |
| static uint dgap_config_get_num_prts(struct board_t *bd) |
| { |
| int count = 0; |
| struct cnode *p; |
| |
| if (!bd) |
| return 0; |
| |
| for (p = bd->bd_config; p; p = p->next) { |
| |
| switch (p->type) { |
| case BNODE: |
| /* |
| * check for pcxr types. |
| */ |
| if (p->u.board.type > EPCFE) |
| count += p->u.board.nport; |
| break; |
| case CNODE: |
| count += p->u.conc.nport; |
| break; |
| case MNODE: |
| count += p->u.module.nport; |
| break; |
| } |
| } |
| return count; |
| } |
| |
| static char *dgap_create_config_string(struct board_t *bd, char *string) |
| { |
| char *ptr = string; |
| struct cnode *p; |
| struct cnode *q; |
| int speed; |
| |
| if (!bd) { |
| *ptr = 0xff; |
| return string; |
| } |
| |
| for (p = bd->bd_config; p; p = p->next) { |
| |
| switch (p->type) { |
| case LNODE: |
| *ptr = '\0'; |
| ptr++; |
| *ptr = p->u.line.speed; |
| ptr++; |
| break; |
| case CNODE: |
| /* |
| * Because the EPC/con concentrators can have EM modules |
| * hanging off of them, we have to walk ahead in the |
| * list and keep adding the number of ports on each EM |
| * to the config. UGH! |
| */ |
| speed = p->u.conc.speed; |
| q = p->next; |
| if (q && (q->type == MNODE)) { |
| *ptr = (p->u.conc.nport + 0x80); |
| ptr++; |
| p = q; |
| while (q->next && (q->next->type) == MNODE) { |
| *ptr = (q->u.module.nport + 0x80); |
| ptr++; |
| p = q; |
| q = q->next; |
| } |
| *ptr = q->u.module.nport; |
| ptr++; |
| } else { |
| *ptr = p->u.conc.nport; |
| ptr++; |
| } |
| |
| *ptr = speed; |
| ptr++; |
| break; |
| } |
| } |
| |
| *ptr = 0xff; |
| return string; |
| } |
| |
| /* |
| * Parse a configuration file read into memory as a string. |
| */ |
| static int dgap_parsefile(char **in) |
| { |
| struct cnode *p, *brd, *line, *conc; |
| int rc; |
| char *s; |
| int linecnt = 0; |
| |
| p = &dgap_head; |
| brd = line = conc = NULL; |
| |
| /* perhaps we are adding to an existing list? */ |
| while (p->next) |
| p = p->next; |
| |
| /* file must start with a BEGIN */ |
| while ((rc = dgap_gettok(in)) != BEGIN) { |
| if (rc == 0) { |
| pr_err("unexpected EOF"); |
| return -1; |
| } |
| } |
| |
| for (; ;) { |
| int board_type = 0; |
| int conc_type = 0; |
| int module_type = 0; |
| |
| rc = dgap_gettok(in); |
| if (rc == 0) { |
| pr_err("unexpected EOF"); |
| return -1; |
| } |
| |
| switch (rc) { |
| case BEGIN: /* should only be 1 begin */ |
| pr_err("unexpected config_begin\n"); |
| return -1; |
| |
| case END: |
| return 0; |
| |
| case BOARD: /* board info */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| |
| p->type = BNODE; |
| p->u.board.status = kstrdup("No", GFP_KERNEL); |
| line = conc = NULL; |
| brd = p; |
| linecnt = -1; |
| |
| board_type = dgap_gettok(in); |
| if (board_type == 0) { |
| pr_err("board !!type not specified"); |
| return -1; |
| } |
| |
| p->u.board.type = board_type; |
| |
| break; |
| |
| case IO: /* i/o port */ |
| if (p->type != BNODE) { |
| pr_err("IO port only valid for boards"); |
| return -1; |
| } |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.board.portstr = kstrdup(s, GFP_KERNEL); |
| if (kstrtol(s, 0, &p->u.board.port)) { |
| pr_err("bad number for IO port"); |
| return -1; |
| } |
| p->u.board.v_port = 1; |
| break; |
| |
| case MEM: /* memory address */ |
| if (p->type != BNODE) { |
| pr_err("memory address only valid for boards"); |
| return -1; |
| } |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.board.addrstr = kstrdup(s, GFP_KERNEL); |
| if (kstrtoul(s, 0, &p->u.board.addr)) { |
| pr_err("bad number for memory address"); |
| return -1; |
| } |
| p->u.board.v_addr = 1; |
| break; |
| |
| case PCIINFO: /* pci information */ |
| if (p->type != BNODE) { |
| pr_err("memory address only valid for boards"); |
| return -1; |
| } |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.board.pcibusstr = kstrdup(s, GFP_KERNEL); |
| if (kstrtoul(s, 0, &p->u.board.pcibus)) { |
| pr_err("bad number for pci bus"); |
| return -1; |
| } |
| p->u.board.v_pcibus = 1; |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.board.pcislotstr = kstrdup(s, GFP_KERNEL); |
| if (kstrtoul(s, 0, &p->u.board.pcislot)) { |
| pr_err("bad number for pci slot"); |
| return -1; |
| } |
| p->u.board.v_pcislot = 1; |
| break; |
| |
| case METHOD: |
| if (p->type != BNODE) { |
| pr_err("install method only valid for boards"); |
| return -1; |
| } |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.board.method = kstrdup(s, GFP_KERNEL); |
| p->u.board.v_method = 1; |
| break; |
| |
| case STATUS: |
| if (p->type != BNODE) { |
| pr_err("config status only valid for boards"); |
| return -1; |
| } |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.board.status = kstrdup(s, GFP_KERNEL); |
| break; |
| |
| case NPORTS: /* number of ports */ |
| if (p->type == BNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.board.nport)) { |
| pr_err("bad number for number of ports"); |
| return -1; |
| } |
| p->u.board.v_nport = 1; |
| } else if (p->type == CNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.conc.nport)) { |
| pr_err("bad number for number of ports"); |
| return -1; |
| } |
| p->u.conc.v_nport = 1; |
| } else if (p->type == MNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.module.nport)) { |
| pr_err("bad number for number of ports"); |
| return -1; |
| } |
| p->u.module.v_nport = 1; |
| } else { |
| pr_err("nports only valid for concentrators or modules"); |
| return -1; |
| } |
| break; |
| |
| case ID: /* letter ID used in tty name */ |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| |
| p->u.board.status = kstrdup(s, GFP_KERNEL); |
| |
| if (p->type == CNODE) { |
| p->u.conc.id = kstrdup(s, GFP_KERNEL); |
| p->u.conc.v_id = 1; |
| } else if (p->type == MNODE) { |
| p->u.module.id = kstrdup(s, GFP_KERNEL); |
| p->u.module.v_id = 1; |
| } else { |
| pr_err("id only valid for concentrators or modules"); |
| return -1; |
| } |
| break; |
| |
| case STARTO: /* start offset of ID */ |
| if (p->type == BNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.board.start)) { |
| pr_err("bad number for start of tty count"); |
| return -1; |
| } |
| p->u.board.v_start = 1; |
| } else if (p->type == CNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.conc.start)) { |
| pr_err("bad number for start of tty count"); |
| return -1; |
| } |
| p->u.conc.v_start = 1; |
| } else if (p->type == MNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.module.start)) { |
| pr_err("bad number for start of tty count"); |
| return -1; |
| } |
| p->u.module.v_start = 1; |
| } else { |
| pr_err("start only valid for concentrators or modules"); |
| return -1; |
| } |
| break; |
| |
| case TTYN: /* tty name prefix */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = TNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpeced end of file"); |
| return -1; |
| } |
| p->u.ttyname = kstrdup(s, GFP_KERNEL); |
| if (!p->u.ttyname) |
| return -1; |
| |
| break; |
| |
| case CU: /* cu name prefix */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = CUNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpeced end of file"); |
| return -1; |
| } |
| p->u.cuname = kstrdup(s, GFP_KERNEL); |
| if (!p->u.cuname) |
| return -1; |
| |
| break; |
| |
| case LINE: /* line information */ |
| if (dgap_checknode(p)) |
| return -1; |
| if (!brd) { |
| pr_err("must specify board before line info"); |
| return -1; |
| } |
| switch (brd->u.board.type) { |
| case PPCM: |
| pr_err("line not valid for PC/em"); |
| return -1; |
| } |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = LNODE; |
| conc = NULL; |
| line = p; |
| linecnt++; |
| break; |
| |
| case CONC: /* concentrator information */ |
| if (dgap_checknode(p)) |
| return -1; |
| if (!line) { |
| pr_err("must specify line info before concentrator"); |
| return -1; |
| } |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = CNODE; |
| conc = p; |
| |
| if (linecnt) |
| brd->u.board.conc2++; |
| else |
| brd->u.board.conc1++; |
| |
| conc_type = dgap_gettok(in); |
| if (conc_type == 0 || (conc_type != CX && |
| conc_type != EPC)) { |
| pr_err("failed to set a type of concentratros"); |
| return -1; |
| } |
| |
| p->u.conc.type = conc_type; |
| |
| break; |
| |
| case MOD: /* EBI module */ |
| if (dgap_checknode(p)) |
| return -1; |
| if (!brd) { |
| pr_err("must specify board info before EBI modules"); |
| return -1; |
| } |
| switch (brd->u.board.type) { |
| case PPCM: |
| linecnt = 0; |
| break; |
| default: |
| if (!conc) { |
| pr_err("must specify concentrator info before EBI module"); |
| return -1; |
| } |
| } |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = MNODE; |
| |
| if (linecnt) |
| brd->u.board.module2++; |
| else |
| brd->u.board.module1++; |
| |
| module_type = dgap_gettok(in); |
| if (module_type == 0 || (module_type != PORTS && |
| module_type != MODEM)) { |
| pr_err("failed to set a type of module"); |
| return -1; |
| } |
| |
| p->u.module.type = module_type; |
| |
| break; |
| |
| case CABLE: |
| if (p->type == LNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.line.cable = kstrdup(s, GFP_KERNEL); |
| p->u.line.v_cable = 1; |
| } |
| break; |
| |
| case SPEED: /* sync line speed indication */ |
| if (p->type == LNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.line.speed)) { |
| pr_err("bad number for line speed"); |
| return -1; |
| } |
| p->u.line.v_speed = 1; |
| } else if (p->type == CNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.conc.speed)) { |
| pr_err("bad number for line speed"); |
| return -1; |
| } |
| p->u.conc.v_speed = 1; |
| } else { |
| pr_err("speed valid only for lines or concentrators."); |
| return -1; |
| } |
| break; |
| |
| case CONNECT: |
| if (p->type == CNODE) { |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| p->u.conc.connect = kstrdup(s, GFP_KERNEL); |
| p->u.conc.v_connect = 1; |
| } |
| break; |
| case PRINT: /* transparent print name prefix */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = PNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpeced end of file"); |
| return -1; |
| } |
| p->u.printname = kstrdup(s, GFP_KERNEL); |
| if (!p->u.printname) |
| return -1; |
| |
| break; |
| |
| case CMAJOR: /* major number */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = JNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.majornumber)) { |
| pr_err("bad number for major number"); |
| return -1; |
| } |
| break; |
| |
| case ALTPIN: /* altpin setting */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = ANODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.altpin)) { |
| pr_err("bad number for altpin"); |
| return -1; |
| } |
| break; |
| |
| case USEINTR: /* enable interrupt setting */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = INTRNODE; |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.useintr)) { |
| pr_err("bad number for useintr"); |
| return -1; |
| } |
| break; |
| |
| case TTSIZ: /* size of tty structure */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = TSNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.ttysize)) { |
| pr_err("bad number for ttysize"); |
| return -1; |
| } |
| break; |
| |
| case CHSIZ: /* channel structure size */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = CSNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.chsize)) { |
| pr_err("bad number for chsize"); |
| return -1; |
| } |
| break; |
| |
| case BSSIZ: /* board structure size */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = BSNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.bssize)) { |
| pr_err("bad number for bssize"); |
| return -1; |
| } |
| break; |
| |
| case UNTSIZ: /* sched structure size */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = USNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.unsize)) { |
| pr_err("bad number for schedsize"); |
| return -1; |
| } |
| break; |
| |
| case F2SIZ: /* f2200 structure size */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = FSNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.f2size)) { |
| pr_err("bad number for f2200size"); |
| return -1; |
| } |
| break; |
| |
| case VPSIZ: /* vpix structure size */ |
| if (dgap_checknode(p)) |
| return -1; |
| |
| p->next = kzalloc(sizeof(struct cnode), GFP_KERNEL); |
| if (!p->next) |
| return -1; |
| |
| p = p->next; |
| p->type = VSNODE; |
| |
| s = dgap_getword(in); |
| if (!s) { |
| pr_err("unexpected end of file"); |
| return -1; |
| } |
| if (kstrtol(s, 0, &p->u.vpixsize)) { |
| pr_err("bad number for vpixsize"); |
| return -1; |
| } |
| break; |
| } |
| } |
| } |
| |
| static void dgap_cleanup_nodes(void) |
| { |
| struct cnode *p; |
| |
| p = &dgap_head; |
| |
| while (p) { |
| struct cnode *tmp = p->next; |
| |
| if (p->type == NULLNODE) { |
| p = tmp; |
| continue; |
| } |
| |
| switch (p->type) { |
| case BNODE: |
| kfree(p->u.board.portstr); |
| kfree(p->u.board.addrstr); |
| kfree(p->u.board.pcibusstr); |
| kfree(p->u.board.pcislotstr); |
| kfree(p->u.board.method); |
| break; |
| case CNODE: |
| kfree(p->u.conc.id); |
| kfree(p->u.conc.connect); |
| break; |
| case MNODE: |
| kfree(p->u.module.id); |
| break; |
| case TNODE: |
| kfree(p->u.ttyname); |
| break; |
| case CUNODE: |
| kfree(p->u.cuname); |
| break; |
| case LNODE: |
| kfree(p->u.line.cable); |
| break; |
| case PNODE: |
| kfree(p->u.printname); |
| break; |
| } |
| |
| kfree(p->u.board.status); |
| kfree(p); |
| p = tmp; |
| } |
| } |
| |
| /* |
| * Retrives the current custom baud rate from FEP memory, |
| * and returns it back to the user. |
| * Returns 0 on error. |
| */ |
| static uint dgap_get_custom_baud(struct channel_t *ch) |
| { |
| u8 __iomem *vaddr; |
| ulong offset; |
| |
| 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 = (ioread16(vaddr + ECS_SEG) << 4) + (ch->ch_portnum * 0x28) |
| + LINE_SPEED; |
| |
| return readw(vaddr + offset); |
| } |
| |
| /* |
| * Remap PCI memory. |
| */ |
| static int dgap_remap(struct board_t *brd) |
| { |
| if (!brd || brd->magic != DGAP_BOARD_MAGIC) |
| return -EIO; |
| |
| if (!request_mem_region(brd->membase, 0x200000, "dgap")) |
| return -ENOMEM; |
| |
| if (!request_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000, |
| "dgap")) |
| goto err_req_mem; |
| |
| brd->re_map_membase = ioremap(brd->membase, 0x200000); |
| if (!brd->re_map_membase) |
| goto err_remap_mem; |
| |
| brd->re_map_port = ioremap((brd->membase + PCI_IO_OFFSET), 0x200000); |
| if (!brd->re_map_port) |
| goto err_remap_port; |
| |
| return 0; |
| |
| err_remap_port: |
| iounmap(brd->re_map_membase); |
| err_remap_mem: |
| release_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000); |
| err_req_mem: |
| release_mem_region(brd->membase, 0x200000); |
| |
| return -ENOMEM; |
| } |
| |
| static void dgap_unmap(struct board_t *brd) |
| { |
| iounmap(brd->re_map_port); |
| iounmap(brd->re_map_membase); |
| release_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000); |
| release_mem_region(brd->membase, 0x200000); |
| } |
| |
| /* |
| * dgap_parity_scan() |
| * |
| * Convert the FEP5 way of reporting parity errors and breaks into |
| * the Linux line discipline way. |
| */ |
| static 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; |
| |
| 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) { |
| ch->ch_err_break++; |
| *fout++ = TTY_BREAK; |
| } else { |
| ch->ch_err_parity++; |
| *fout++ = TTY_PARITY; |
| } |
| } |
| |
| count += 1; |
| ch->pscan_state = 0; |
| } |
| } |
| *len = count; |
| } |
| |
| /*======================================================================= |
| * |
| * dgap_input - Process received data. |
| * |
| * ch - Pointer to channel structure. |
| * |
| *=======================================================================*/ |
| |
| static void dgap_input(struct channel_t *ch) |
| { |
| struct board_t *bd; |
| struct bs_t __iomem *bs; |
| struct tty_struct *tp; |
| struct tty_ldisc *ld; |
| uint rmask; |
| uint head; |
| uint tail; |
| int data_len; |
| ulong lock_flags; |
| ulong lock_flags2; |
| int flip_len; |
| int len; |
| int n; |
| u8 *buf; |
| u8 tmpchar; |
| int s; |
| |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| tp = ch->ch_tun.un_tty; |
| |
| bs = ch->ch_bs; |
| if (!bs) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| /* |
| * Figure the number of characters in the buffer. |
| * Exit immediately if none. |
| */ |
| |
| rmask = ch->ch_rsize - 1; |
| |
| head = readw(&(bs->rx_head)); |
| head &= rmask; |
| tail = readw(&(bs->rx_tail)); |
| tail &= rmask; |
| |
| data_len = (head - tail) & rmask; |
| |
| if (data_len == 0) { |
| writeb(1, &(bs->idata)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return; |
| } |
| |
| /* |
| * If the device is not open, or CREAD is off, flush |
| * input data and return immediately. |
| */ |
| if ((bd->state != BOARD_READY) || !tp || |
| (tp->magic != TTY_MAGIC) || |
| !(ch->ch_tun.un_flags & UN_ISOPEN) || |
| !(tp->termios.c_cflag & CREAD) || |
| (ch->ch_tun.un_flags & UN_CLOSING)) { |
| |
| writew(head, &(bs->rx_tail)); |
| writeb(1, &(bs->idata)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return; |
| } |
| |
| /* |
| * If we are throttled, simply don't read any data. |
| */ |
| if (ch->ch_flags & CH_RXBLOCK) { |
| writeb(1, &(bs->idata)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return; |
| } |
| |
| /* |
| * Ignore oruns. |
| */ |
| tmpchar = readb(&(bs->orun)); |
| if (tmpchar) { |
| ch->ch_err_overrun++; |
| writeb(0, &(bs->orun)); |
| } |
| |
| /* Decide how much data we can send into the tty layer */ |
| flip_len = TTY_FLIPBUF_SIZE; |
| |
| /* Chop down the length, if needed */ |
| len = min(data_len, flip_len); |
| len = min(len, (N_TTY_BUF_SIZE - 1)); |
| |
| ld = tty_ldisc_ref(tp); |
| |
| #ifdef TTY_DONT_FLIP |
| /* |
| * If the DONT_FLIP flag is on, don't flush our buffer, and act |
| * like the ld doesn't have any space to put the data right now. |
| */ |
| if (test_bit(TTY_DONT_FLIP, &tp->flags)) |
| len = 0; |
| #endif |
| |
| /* |
| * If we were unable to get a reference to the ld, |
| * don't flush our buffer, and act like the ld doesn't |
| * have any space to put the data right now. |
| */ |
| if (!ld) { |
| len = 0; |
| } else { |
| /* |
| * If ld doesn't have a pointer to a receive_buf function, |
| * flush the data, then act like the ld doesn't have any |
| * space to put the data right now. |
| */ |
| if (!ld->ops->receive_buf) { |
| writew(head, &(bs->rx_tail)); |
| len = 0; |
| } |
| } |
| |
| if (len <= 0) { |
| writeb(1, &(bs->idata)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| if (ld) |
| tty_ldisc_deref(ld); |
| return; |
| } |
| |
| buf = ch->ch_bd->flipbuf; |
| n = len; |
| |
| /* |
| * n now contains the most amount of data we can copy, |
| * bounded either by our buffer size or the amount |
| * of data the card actually has pending... |
| */ |
| while (n) { |
| |
| s = ((head >= tail) ? head : ch->ch_rsize) - tail; |
| s = min(s, n); |
| |
| if (s <= 0) |
| break; |
| |
| memcpy_fromio(buf, ch->ch_raddr + tail, s); |
| |
| tail += s; |
| buf += s; |
| |
| n -= s; |
| /* Flip queue if needed */ |
| tail &= rmask; |
| } |
| |
| writew(tail, &(bs->rx_tail)); |
| writeb(1, &(bs->idata)); |
| ch->ch_rxcount += len; |
| |
| /* |
| * If we are completely raw, we don't need to go through a lot |
| * of the tty layers that exist. |
| * In this case, we take the shortest and fastest route we |
| * can to relay the data to the user. |
| * |
| * On the other hand, if we are not raw, we need to go through |
| * the tty layer, which has its API more well defined. |
| */ |
| if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) { |
| dgap_parity_scan(ch, ch->ch_bd->flipbuf, |
| ch->ch_bd->flipflagbuf, &len); |
| |
| len = tty_buffer_request_room(tp->port, len); |
| tty_insert_flip_string_flags(tp->port, ch->ch_bd->flipbuf, |
| ch->ch_bd->flipflagbuf, len); |
| } else { |
| len = tty_buffer_request_room(tp->port, len); |
| tty_insert_flip_string(tp->port, ch->ch_bd->flipbuf, len); |
| } |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| /* Tell the tty layer its okay to "eat" the data now */ |
| tty_flip_buffer_push(tp->port); |
| |
| if (ld) |
| tty_ldisc_deref(ld); |
| |
| } |
| |
| static void dgap_write_wakeup(struct board_t *bd, struct channel_t *ch, |
| struct un_t *un, u32 mask, |
| unsigned long *irq_flags1, |
| unsigned long *irq_flags2) |
| { |
| if (!(un->un_flags & mask)) |
| return; |
| |
| un->un_flags &= ~mask; |
| |
| if (!(un->un_flags & UN_ISOPEN)) |
| return; |
| |
| if ((un->un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && |
| un->un_tty->ldisc->ops->write_wakeup) { |
| spin_unlock_irqrestore(&ch->ch_lock, *irq_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, *irq_flags1); |
| |
| (un->un_tty->ldisc->ops->write_wakeup)(un->un_tty); |
| |
| spin_lock_irqsave(&bd->bd_lock, *irq_flags1); |
| spin_lock_irqsave(&ch->ch_lock, *irq_flags2); |
| } |
| wake_up_interruptible(&un->un_tty->write_wait); |
| wake_up_interruptible(&un->un_flags_wait); |
| } |
| |
| /************************************************************************ |
| * Determines when CARRIER changes state and takes appropriate |
| * action. |
| ************************************************************************/ |
| static void dgap_carrier(struct channel_t *ch) |
| { |
| struct board_t *bd; |
| |
| int virt_carrier = 0; |
| int phys_carrier = 0; |
| |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| /* Make sure altpin is always set correctly */ |
| if (ch->ch_digi.digi_flags & DIGI_ALTPIN) { |
| ch->ch_dsr = DM_CD; |
| ch->ch_cd = DM_DSR; |
| } else { |
| ch->ch_dsr = DM_DSR; |
| ch->ch_cd = DM_CD; |
| } |
| |
| if (ch->ch_mistat & D_CD(ch)) |
| phys_carrier = 1; |
| |
| if (ch->ch_digi.digi_flags & DIGI_FORCEDCD) |
| virt_carrier = 1; |
| |
| if (ch->ch_c_cflag & CLOCAL) |
| virt_carrier = 1; |
| |
| /* |
| * Test for a VIRTUAL carrier transition to HIGH. |
| */ |
| if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) { |
| |
| /* |
| * When carrier rises, wake any threads waiting |
| * for carrier in the open routine. |
| */ |
| |
| if (waitqueue_active(&(ch->ch_flags_wait))) |
| wake_up_interruptible(&ch->ch_flags_wait); |
| } |
| |
| /* |
| * Test for a PHYSICAL carrier transition to HIGH. |
| */ |
| if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) { |
| |
| /* |
| * When carrier rises, wake any threads waiting |
| * for carrier in the open routine. |
| */ |
| |
| if (waitqueue_active(&(ch->ch_flags_wait))) |
| wake_up_interruptible(&ch->ch_flags_wait); |
| } |
| |
| /* |
| * Test for a PHYSICAL transition to low, so long as we aren't |
| * currently ignoring physical transitions (which is what "virtual |
| * carrier" indicates). |
| * |
| * The transition of the virtual carrier to low really doesn't |
| * matter... it really only means "ignore carrier state", not |
| * "make pretend that carrier is there". |
| */ |
| if ((virt_carrier == 0) && |
| ((ch->ch_flags & CH_CD) != 0) && |
| (phys_carrier == 0)) { |
| |
| /* |
| * When carrier drops: |
| * |
| * Drop carrier on all open units. |
| * |
| * Flush queues, waking up any task waiting in the |
| * line discipline. |
| * |
| * Send a hangup to the control terminal. |
| * |
| * Enable all select calls. |
| */ |
| if (waitqueue_active(&(ch->ch_flags_wait))) |
| wake_up_interruptible(&ch->ch_flags_wait); |
| |
| if (ch->ch_tun.un_open_count > 0) |
| tty_hangup(ch->ch_tun.un_tty); |
| |
| if (ch->ch_pun.un_open_count > 0) |
| tty_hangup(ch->ch_pun.un_tty); |
| } |
| |
| /* |
| * Make sure that our cached values reflect the current reality. |
| */ |
| if (virt_carrier == 1) |
| ch->ch_flags |= CH_FCAR; |
| else |
| ch->ch_flags &= ~CH_FCAR; |
| |
| if (phys_carrier == 1) |
| ch->ch_flags |= CH_CD; |
| else |
| ch->ch_flags &= ~CH_CD; |
| } |
| |
| /*======================================================================= |
| * |
| * 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 __iomem *bs; |
| u8 __iomem *event; |
| u8 __iomem *vaddr; |
| struct ev_t __iomem *eaddr; |
| uint head; |
| uint tail; |
| int port; |
| int reason; |
| int modem; |
| int b1; |
| |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return -EIO; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| |
| vaddr = bd->re_map_membase; |
| |
| if (!vaddr) { |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| eaddr = (struct ev_t __iomem *) (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) { |
| /* Let go of board lock */ |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| /* |
| * Loop to process all the events in the buffer. |
| */ |
| while (tail != head) { |
| |
| /* |
| * Get interrupt information. |
| */ |
| |
| event = bd->re_map_membase + tail + EVSTART; |
| |
| port = ioread8(event); |
| reason = ioread8(event + 1); |
| modem = ioread8(event + 2); |
| b1 = ioread8(event + 3); |
| |
| /* |
| * 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. |
| */ |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| bs = ch->ch_bs; |
| |
| if (!bs) { |
| spin_unlock_irqrestore(&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. |
| */ |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| dgap_input(ch); |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&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) { |
| |
| 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) { |
| dgap_write_wakeup(bd, ch, &ch->ch_tun, UN_LOW, |
| &lock_flags, &lock_flags2); |
| dgap_write_wakeup(bd, ch, &ch->ch_pun, UN_LOW, |
| &lock_flags, &lock_flags2); |
| if (ch->ch_flags & CH_WLOW) { |
| ch->ch_flags &= ~CH_WLOW; |
| wake_up_interruptible(&ch->ch_flags_wait); |
| } |
| } |
| |
| /* |
| * Process Transmit empty. |
| */ |
| if (reason & IFTEM) { |
| dgap_write_wakeup(bd, ch, &ch->ch_tun, UN_EMPTY, |
| &lock_flags, &lock_flags2); |
| dgap_write_wakeup(bd, ch, &ch->ch_pun, UN_EMPTY, |
| &lock_flags, &lock_flags2); |
| if (ch->ch_flags & CH_WEMPTY) { |
| ch->ch_flags &= ~CH_WEMPTY; |
| wake_up_interruptible(&ch->ch_flags_wait); |
| } |
| } |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| |
| next: |
| tail = (tail + 4) & (EVMAX - EVSTART - 4); |
| } |
| |
| writew(tail, &(eaddr->ev_tail)); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| } |
| |
| /* |
| * Our board poller function. |
| */ |
| static void dgap_poll_tasklet(unsigned long data) |
| { |
| struct board_t *bd = (struct board_t *) data; |
| ulong lock_flags; |
| char __iomem *vaddr; |
| u16 head, tail; |
| |
| if (!bd || (bd->magic != DGAP_BOARD_MAGIC)) |
| return; |
| |
| if (bd->inhibit_poller) |
| return; |
| |
| spin_lock_irqsave(&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 __iomem *eaddr; |
| |
| if (!bd->re_map_membase) { |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return; |
| } |
| if (!bd->re_map_port) { |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return; |
| } |
| |
| if (!bd->nasync) |
| goto out; |
| |
| eaddr = (struct ev_t __iomem *) (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) { |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| dgap_event(bd); |
| spin_lock_irqsave(&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); |
| |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return; |
| } |
| |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| /* |
| * dgap_found_board() |
| * |
| * A board has been found, init it. |
| */ |
| static struct board_t *dgap_found_board(struct pci_dev *pdev, int id, |
| int boardnum) |
| { |
| struct board_t *brd; |
| unsigned int pci_irq; |
| int i; |
| int ret; |
| |
| /* get the board structure and prep it */ |
| brd = kzalloc(sizeof(struct board_t), GFP_KERNEL); |
| if (!brd) |
| return ERR_PTR(-ENOMEM); |
| |
| /* store the info for the board we've found */ |
| brd->magic = DGAP_BOARD_MAGIC; |
| brd->boardnum = boardnum; |
| brd->vendor = dgap_pci_tbl[id].vendor; |
| brd->device = dgap_pci_tbl[id].device; |
| brd->pdev = pdev; |
| brd->pci_bus = pdev->bus->number; |
| brd->pci_slot = PCI_SLOT(pdev->devfn); |
| brd->name = dgap_ids[id].name; |
| brd->maxports = dgap_ids[id].maxports; |
| brd->type = dgap_ids[id].config_type; |
| brd->dpatype = dgap_ids[id].dpatype; |
| brd->dpastatus = BD_NOFEP; |
| init_waitqueue_head(&brd->state_wait); |
| |
| spin_lock_init(&brd->bd_lock); |
| |
| brd->inhibit_poller = FALSE; |
| brd->wait_for_bios = 0; |
| brd->wait_for_fep = 0; |
| |
| for (i = 0; i < MAXPORTS; i++) |
| brd->channels[i] = NULL; |
| |
| /* store which card & revision we have */ |
| pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor); |
| pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice); |
| pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev); |
| |
| pci_irq = pdev->irq; |
| brd->irq = pci_irq; |
| |
| /* get the PCI Base Address Registers */ |
| |
| /* Xr Jupiter and EPC use BAR 2 */ |
| if (brd->device == PCI_DEV_XRJ_DID || brd->device == PCI_DEV_EPCJ_DID) { |
| brd->membase = pci_resource_start(pdev, 2); |
| brd->membase_end = pci_resource_end(pdev, 2); |
| } |
| /* Everyone else uses BAR 0 */ |
| else { |
| brd->membase = pci_resource_start(pdev, 0); |
| brd->membase_end = pci_resource_end(pdev, 0); |
| } |
| |
| if (!brd->membase) { |
| ret = -ENODEV; |
| goto free_brd; |
| } |
| |
| if (brd->membase & 1) |
| brd->membase &= ~3; |
| else |
| brd->membase &= ~15; |
| |
| /* |
| * On the PCI boards, there is no IO space allocated |
| * The I/O registers will be in the first 3 bytes of the |
| * upper 2MB of the 4MB memory space. The board memory |
| * will be mapped into the low 2MB of the 4MB memory space |
| */ |
| brd->port = brd->membase + PCI_IO_OFFSET; |
| brd->port_end = brd->port + PCI_IO_SIZE_DGAP; |
| |
| /* |
| * Special initialization for non-PLX boards |
| */ |
| if (brd->device != PCI_DEV_XRJ_DID && brd->device != PCI_DEV_EPCJ_DID) { |
| unsigned short cmd; |
| |
| pci_write_config_byte(pdev, 0x40, 0); |
| pci_write_config_byte(pdev, 0x46, 0); |
| |
| /* Limit burst length to 2 doubleword transactions */ |
| pci_write_config_byte(pdev, 0x42, 1); |
| |
| /* |
| * Enable IO and mem if not already done. |
| * This was needed for support on Itanium. |
| */ |
| pci_read_config_word(pdev, PCI_COMMAND, &cmd); |
| cmd |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY); |
| pci_write_config_word(pdev, PCI_COMMAND, cmd); |
| } |
| |
| /* init our poll helper tasklet */ |
| tasklet_init(&brd->helper_tasklet, dgap_poll_tasklet, |
| (unsigned long) brd); |
| |
| ret = dgap_remap(brd); |
| if (ret) |
| goto free_brd; |
| |
| pr_info("dgap: board %d: %s (rev %d), irq %ld\n", |
| boardnum, brd->name, brd->rev, brd->irq); |
| |
| return brd; |
| |
| free_brd: |
| kfree(brd); |
| |
| return ERR_PTR(ret); |
| } |
| |
| /* |
| * dgap_intr() |
| * |
| * Driver interrupt handler. |
| */ |
| static irqreturn_t dgap_intr(int irq, void *voidbrd) |
| { |
| struct board_t *brd = voidbrd; |
| |
| if (!brd) |
| return IRQ_NONE; |
| |
| /* |
| * Check to make sure its for us. |
| */ |
| if (brd->magic != DGAP_BOARD_MAGIC) |
| return IRQ_NONE; |
| |
| brd->intr_count++; |
| |
| /* |
| * Schedule tasklet to run at a better time. |
| */ |
| tasklet_schedule(&brd->helper_tasklet); |
| return IRQ_HANDLED; |
| } |
| |
| /***************************************************************************** |
| * |
| * Function: |
| * |
| * dgap_poll_handler |
| * |
| * Author: |
| * |
| * Scott H Kilau |
| * |
| * Parameters: |
| * |
| * dummy -- ignored |
| * |
| * Return Values: |
| * |
| * none |
| * |
| * Description: |
| * |
| * As each timer expires, it determines (a) whether the "transmit" |
| * waiter needs to be woken up, and (b) whether the poller needs to |
| * be rescheduled. |
| * |
| ******************************************************************************/ |
| |
| static void dgap_poll_handler(ulong dummy) |
| { |
| unsigned int i; |
| struct board_t *brd; |
| unsigned long lock_flags; |
| ulong new_time; |
| |
| dgap_poll_counter++; |
| |
| /* |
| * Do not start the board state machine until |
| * driver tells us its up and running, and has |
| * everything it needs. |
| */ |
| if (dgap_driver_state != DRIVER_READY) |
| goto schedule_poller; |
| |
| /* |
| * If we have just 1 board, or the system is not SMP, |
| * then use the typical old style poller. |
| * Otherwise, use our new tasklet based poller, which should |
| * speed things up for multiple boards. |
| */ |
| if ((dgap_numboards == 1) || (num_online_cpus() <= 1)) { |
| for (i = 0; i < dgap_numboards; i++) { |
| |
| brd = dgap_board[i]; |
| |
| if (brd->state == BOARD_FAILED) |
| continue; |
| if (!brd->intr_running) |
| /* Call the real board poller directly */ |
| dgap_poll_tasklet((unsigned long) brd); |
| } |
| } else { |
| /* |
| * Go thru each board, kicking off a |
| * tasklet for each if needed |
| */ |
| for (i = 0; i < dgap_numboards; i++) { |
| brd = dgap_board[i]; |
| |
| /* |
| * Attempt to grab the board lock. |
| * |
| * If we can't get it, no big deal, the next poll |
| * will get it. Basically, I just really don't want |
| * to spin in here, because I want to kick off my |
| * tasklets as fast as I can, and then get out the |
| * poller. |
| */ |
| if (!spin_trylock(&brd->bd_lock)) |
| continue; |
| |
| /* |
| * If board is in a failed state, don't bother |
| * scheduling a tasklet |
| */ |
| if (brd->state == BOARD_FAILED) { |
| spin_unlock(&brd->bd_lock); |
| continue; |
| } |
| |
| /* Schedule a poll helper task */ |
| if (!brd->intr_running) |
| tasklet_schedule(&brd->helper_tasklet); |
| |
| /* |
| * Can't do DGAP_UNLOCK here, as we don't have |
| * lock_flags because we did a trylock above. |
| */ |
| spin_unlock(&brd->bd_lock); |
| } |
| } |
| |
| schedule_poller: |
| |
| /* |
| * Schedule ourself back at the nominal wakeup interval. |
| */ |
| spin_lock_irqsave(&dgap_poll_lock, lock_flags); |
| dgap_poll_time += dgap_jiffies_from_ms(dgap_poll_tick); |
| |
| new_time = dgap_poll_time - jiffies; |
| |
| if ((ulong) new_time >= 2 * dgap_poll_tick) { |
| dgap_poll_time = |
| jiffies + dgap_jiffies_from_ms(dgap_poll_tick); |
| } |
| |
| dgap_poll_timer.function = dgap_poll_handler; |
| dgap_poll_timer.data = 0; |
| dgap_poll_timer.expires = dgap_poll_time; |
| spin_unlock_irqrestore(&dgap_poll_lock, lock_flags); |
| |
| if (!dgap_poll_stop) |
| add_timer(&dgap_poll_timer); |
| } |
| |
| /*======================================================================= |
| * |
| * 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. |
| * |
| *=======================================================================*/ |
| static void dgap_cmdb(struct channel_t *ch, u8 cmd, u8 byte1, |
| u8 byte2, uint ncmds) |
| { |
| char __iomem *vaddr; |
| struct __iomem cm_t *cm_addr; |
| 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) |
| 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 __iomem *) (vaddr + CMDBUF); |
| head = readw(&(cm_addr->cm_head)); |
| |
| /* |
| * Forget it if pointers out of range. |
| */ |
| if (head >= (CMDMAX - CMDSTART) || (head & 03)) { |
| ch->ch_bd->state = BOARD_FAILED; |
| return; |
| } |
| |
| /* |
| * Put the data in the circular command buffer. |
| */ |
| writeb(cmd, (vaddr + head + CMDSTART + 0)); |
| writeb((u8) ch->ch_portnum, (vaddr + head + CMDSTART + 1)); |
| writeb(byte1, (vaddr + head + CMDSTART + 2)); |
| writeb(byte2, (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) { |
| 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. |
| * |
| *=======================================================================*/ |
| static void dgap_cmdw(struct channel_t *ch, u8 cmd, u16 word, uint ncmds) |
| { |
| char __iomem *vaddr; |
| struct __iomem cm_t *cm_addr; |
| 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) |
| 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 __iomem *) (vaddr + CMDBUF); |
| head = readw(&(cm_addr->cm_head)); |
| |
| /* |
| * Forget it if pointers out of range. |
| */ |
| if (head >= (CMDMAX - CMDSTART) || (head & 03)) { |
| ch->ch_bd->state = BOARD_FAILED; |
| return; |
| } |
| |
| /* |
| * Put the data in the circular command buffer. |
| */ |
| writeb(cmd, (vaddr + head + CMDSTART + 0)); |
| writeb((u8) ch->ch_portnum, (vaddr + head + CMDSTART + 1)); |
| writew((u16) word, (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) { |
| 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 __iomem *vaddr; |
| struct __iomem cm_t *cm_addr; |
| 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) |
| 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 __iomem *) (vaddr + CMDBUF); |
| head = readw(&(cm_addr->cm_head)); |
| |
| /* |
| * Forget it if pointers out of range. |
| */ |
| if (head >= (CMDMAX - CMDSTART) || (head & 03)) { |
| 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((u8) 0xff, (vaddr + head + CMDSTART + 0)); |
| |
| writeb((u8) ch->ch_portnum, (vaddr + head + CMDSTART + 1)); |
| writew((u16) cmd, (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, (vaddr + CMDSTART)); |
| else |
| writew((u16) word, (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) { |
| ch->ch_bd->state = BOARD_FAILED; |
| return; |
| } |
| udelay(10); |
| } |
| } |
| |
| /*======================================================================= |
| * |
| * dgap_wmove - Write data to FEP buffer. |
| * |
| * ch - Pointer to channel structure. |
| * buf - Pointer to characters to be moved. |
| * cnt - Number of characters to move. |
| * |
| *=======================================================================*/ |
| static void dgap_wmove(struct channel_t *ch, char *buf, uint cnt) |
| { |
| int n; |
| char __iomem *taddr; |
| struct bs_t __iomem *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) |
| 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)); |
| } |
| |
| /* |
| * Calls the firmware to reset this channel. |
| */ |
| static 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. |
| * |
| *=======================================================================*/ |
| static int dgap_param(struct channel_t *ch, struct board_t *bd, u32 un_type) |
| { |
| u16 head; |
| u16 cflag; |
| u16 iflag; |
| u8 mval; |
| u8 hflow; |
| |
| /* |
| * 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 |
| */ |
| |
| 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_custom_speed = dgap_get_custom_baud(ch); |
| ch->ch_baud_info = 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_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 |
| 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_DEV_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, (u8) 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); |
| |
| if (ch->ch_mostat ^ mval) { |
| ch->ch_mostat = mval; |
| |
| /* Okay to have channel and board locks held calling this */ |
| dgap_cmdb(ch, SMODEM, (u8) mval, D_RTS(ch)|D_DTR(ch), 0); |
| } |
| |
| /* |
| * Read modem signals, and then call carrier function. |
| */ |
| ch->ch_mistat = readb(&(ch->ch_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); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_block_til_ready() |
| * |
| * Wait for DCD, if needed. |
| */ |
| static int dgap_block_til_ready(struct tty_struct *tty, struct file *file, |
| struct channel_t *ch) |
| { |
| int retval = 0; |
| struct un_t *un; |
| ulong lock_flags; |
| uint old_flags; |
| int sleep_on_un_flags; |
| |
| if (!tty || tty->magic != TTY_MAGIC || !file || !ch || |
| ch->magic != DGAP_CHANNEL_MAGIC) |
| return -EIO; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -EIO; |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| ch->ch_wopen++; |
| |
| /* Loop forever */ |
| while (1) { |
| |
| sleep_on_un_flags = 0; |
| |
| /* |
| * If board has failed somehow during our sleep, |
| * bail with error. |
| */ |
| if (ch->ch_bd->state == BOARD_FAILED) { |
| retval = -EIO; |
| break; |
| } |
| |
| /* If tty was hung up, break out of loop and set error. */ |
| if (tty_hung_up_p(file)) { |
| retval = -EAGAIN; |
| break; |
| } |
| |
| /* |
| * If either unit is in the middle of the fragile part of close, |
| * we just cannot touch the channel safely. |
| * Go back to sleep, knowing that when the channel can be |
| * touched safely, the close routine will signal the |
| * ch_wait_flags to wake us back up. |
| */ |
| if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & |
| UN_CLOSING)) { |
| |
| /* |
| * Our conditions to leave cleanly and happily: |
| * 1) NONBLOCKING on the tty is set. |
| * 2) CLOCAL is set. |
| * 3) DCD (fake or real) is active. |
| */ |
| |
| if (file->f_flags & O_NONBLOCK) |
| break; |
| |
| if (tty->flags & (1 << TTY_IO_ERROR)) |
| break; |
| |
| if (ch->ch_flags & CH_CD) |
| break; |
| |
| if (ch->ch_flags & CH_FCAR) |
| break; |
| } else { |
| sleep_on_un_flags = 1; |
| } |
| |
| /* |
| * If there is a signal pending, the user probably |
| * interrupted (ctrl-c) us. |
| * Leave loop with error set. |
| */ |
| if (signal_pending(current)) { |
| retval = -ERESTARTSYS; |
| break; |
| } |
| |
| /* |
| * Store the flags before we let go of channel lock |
| */ |
| if (sleep_on_un_flags) |
| old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags; |
| else |
| old_flags = ch->ch_flags; |
| |
| /* |
| * Let go of channel lock before calling schedule. |
| * Our poller will get any FEP events and wake us up when DCD |
| * eventually goes active. |
| */ |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| /* |
| * Wait for something in the flags to change |
| * from the current value. |
| */ |
| if (sleep_on_un_flags) { |
| retval = wait_event_interruptible(un->un_flags_wait, |
| (old_flags != (ch->ch_tun.un_flags | |
| ch->ch_pun.un_flags))); |
| } else { |
| retval = wait_event_interruptible(ch->ch_flags_wait, |
| (old_flags != ch->ch_flags)); |
| } |
| |
| /* |
| * We got woken up for some reason. |
| * Before looping around, grab our channel lock. |
| */ |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| } |
| |
| ch->ch_wopen--; |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| return retval; |
| } |
| |
| /* |
| * dgap_tty_flush_buffer() |
| * |
| * Flush Tx buffer (make in == out) |
| */ |
| static void dgap_tty_flush_buffer(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| u16 head; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| ch->ch_flags &= ~CH_STOP; |
| head = readw(&(ch->ch_bs->tx_head)); |
| dgap_cmdw(ch, FLUSHTX, (u16) head, 0); |
| dgap_cmdw(ch, RESUMETX, 0, 0); |
| if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) { |
| ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY); |
| wake_up_interruptible(&ch->ch_tun.un_flags_wait); |
| } |
| if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) { |
| ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY); |
| wake_up_interruptible(&ch->ch_pun.un_flags_wait); |
| } |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| if (waitqueue_active(&tty->write_wait)) |
| wake_up_interruptible(&tty->write_wait); |
| tty_wakeup(tty); |
| } |
| |
| /* |
| * dgap_tty_hangup() |
| * |
| * Hangup the port. Like a close, but don't wait for output to drain. |
| */ |
| static void dgap_tty_hangup(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| /* flush the transmit queues */ |
| dgap_tty_flush_buffer(tty); |
| } |
| |
| /* |
| * dgap_tty_chars_in_buffer() |
| * |
| * Return number of characters that have not been transmitted yet. |
| * |
| * This routine is used by the line discipline to determine if there |
| * is data waiting to be transmitted/drained/flushed or not. |
| */ |
| static int dgap_tty_chars_in_buffer(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| struct bs_t __iomem *bs; |
| u8 tbusy; |
| uint chars; |
| u16 thead, ttail, tmask, chead, ctail; |
| ulong lock_flags = 0; |
| ulong lock_flags2 = 0; |
| |
| if (!tty) |
| return 0; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| |
| bs = ch->ch_bs; |
| if (!bs) |
| return 0; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| tmask = (ch->ch_tsize - 1); |
| |
| /* Get Transmit queue pointers */ |
| thead = readw(&(bs->tx_head)) & tmask; |
| ttail = readw(&(bs->tx_tail)) & tmask; |
| |
| /* Get tbusy flag */ |
| tbusy = readb(&(bs->tbusy)); |
| |
| /* Get Command queue pointers */ |
| chead = readw(&(ch->ch_cm->cm_head)); |
| ctail = readw(&(ch->ch_cm->cm_tail)); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| /* |
| * The only way we know for sure if there is no pending |
| * data left to be transferred, is if: |
| * 1) Transmit head and tail are equal (empty). |
| * 2) Command queue head and tail are equal (empty). |
| * 3) The "TBUSY" flag is 0. (Transmitter not busy). |
| */ |
| |
| if ((ttail == thead) && (tbusy == 0) && (chead == ctail)) { |
| chars = 0; |
| } else { |
| if (thead >= ttail) |
| chars = thead - ttail; |
| else |
| chars = thead - ttail + ch->ch_tsize; |
| /* |
| * Fudge factor here. |
| * If chars is zero, we know that the command queue had |
| * something in it or tbusy was set. Because we cannot |
| * be sure if there is still some data to be transmitted, |
| * lets lie, and tell ld we have 1 byte left. |
| */ |
| if (chars == 0) { |
| /* |
| * If TBUSY is still set, and our tx buffers are empty, |
| * force the firmware to send me another wakeup after |
| * TBUSY has been cleared. |
| */ |
| if (tbusy != 0) { |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| un->un_flags |= UN_EMPTY; |
| writeb(1, &(bs->iempty)); |
| spin_unlock_irqrestore(&ch->ch_lock, |
| lock_flags); |
| } |
| chars = 1; |
| } |
| } |
| |
| return chars; |
| } |
| |
| static int dgap_wait_for_drain(struct tty_struct *tty) |
| { |
| struct channel_t *ch; |
| struct un_t *un; |
| struct bs_t __iomem *bs; |
| int ret = 0; |
| uint count = 1; |
| ulong lock_flags = 0; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return -EIO; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -EIO; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return -EIO; |
| |
| bs = ch->ch_bs; |
| if (!bs) |
| return -EIO; |
| |
| /* Loop until data is drained */ |
| while (count != 0) { |
| |
| count = dgap_tty_chars_in_buffer(tty); |
| |
| if (count == 0) |
| break; |
| |
| /* Set flag waiting for drain */ |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| un->un_flags |= UN_EMPTY; |
| writeb(1, &(bs->iempty)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| /* Go to sleep till we get woken up */ |
| ret = wait_event_interruptible(un->un_flags_wait, |
| ((un->un_flags & UN_EMPTY) == 0)); |
| /* If ret is non-zero, user ctrl-c'ed us */ |
| if (ret) |
| break; |
| } |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| un->un_flags &= ~(UN_EMPTY); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| return ret; |
| } |
| |
| /* |
| * dgap_maxcps_room |
| * |
| * Reduces bytes_available to the max number of characters |
| * that can be sent currently given the maxcps value, and |
| * returns the new bytes_available. This only affects printer |
| * output. |
| */ |
| static int dgap_maxcps_room(struct channel_t *ch, struct un_t *un, |
| int bytes_available) |
| { |
| /* |
| * If its not the Transparent print device, return |
| * the full data amount. |
| */ |
| if (un->un_type != DGAP_PRINT) |
| return bytes_available; |
| |
| if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) { |
| int cps_limit = 0; |
| unsigned long current_time = jiffies; |
| unsigned long buffer_time = current_time + |
| (HZ * ch->ch_digi.digi_bufsize) / |
| ch->ch_digi.digi_maxcps; |
| |
| if (ch->ch_cpstime < current_time) { |
| /* buffer is empty */ |
| ch->ch_cpstime = current_time; /* reset ch_cpstime */ |
| cps_limit = ch->ch_digi.digi_bufsize; |
| } else if (ch->ch_cpstime < buffer_time) { |
| /* still room in the buffer */ |
| cps_limit = ((buffer_time - ch->ch_cpstime) * |
| ch->ch_digi.digi_maxcps) / HZ; |
| } else { |
| /* no room in the buffer */ |
| cps_limit = 0; |
| } |
| |
| bytes_available = min(cps_limit, bytes_available); |
| } |
| |
| return bytes_available; |
| } |
| |
| static inline void dgap_set_firmware_event(struct un_t *un, unsigned int event) |
| { |
| struct channel_t *ch; |
| struct bs_t __iomem *bs; |
| |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| bs = ch->ch_bs; |
| if (!bs) |
| return; |
| |
| if ((event & UN_LOW) != 0) { |
| if ((un->un_flags & UN_LOW) == 0) { |
| un->un_flags |= UN_LOW; |
| writeb(1, &(bs->ilow)); |
| } |
| } |
| if ((event & UN_LOW) != 0) { |
| if ((un->un_flags & UN_EMPTY) == 0) { |
| un->un_flags |= UN_EMPTY; |
| writeb(1, &(bs->iempty)); |
| } |
| } |
| } |
| |
| /* |
| * dgap_tty_write_room() |
| * |
| * Return space available in Tx buffer |
| */ |
| static int dgap_tty_write_room(struct tty_struct *tty) |
| { |
| struct channel_t *ch; |
| struct un_t *un; |
| struct bs_t __iomem *bs; |
| u16 head, tail, tmask; |
| int ret; |
| ulong lock_flags = 0; |
| |
| if (!tty) |
| return 0; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| |
| bs = ch->ch_bs; |
| if (!bs) |
| return 0; |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| tmask = ch->ch_tsize - 1; |
| head = readw(&(bs->tx_head)) & tmask; |
| tail = readw(&(bs->tx_tail)) & tmask; |
| |
| ret = tail - head - 1; |
| if (ret < 0) |
| ret += ch->ch_tsize; |
| |
| /* Limit printer to maxcps */ |
| ret = dgap_maxcps_room(ch, un, ret); |
| |
| /* |
| * If we are printer device, leave space for |
| * possibly both the on and off strings. |
| */ |
| if (un->un_type == DGAP_PRINT) { |
| if (!(ch->ch_flags & CH_PRON)) |
| ret -= ch->ch_digi.digi_onlen; |
| ret -= ch->ch_digi.digi_offlen; |
| } else { |
| if (ch->ch_flags & CH_PRON) |
| ret -= ch->ch_digi.digi_offlen; |
| } |
| |
| if (ret < 0) |
| ret = 0; |
| |
| /* |
| * Schedule FEP to wake us up if needed. |
| * |
| * TODO: This might be overkill... |
| * Do we really need to schedule callbacks from the FEP |
| * in every case? Can we get smarter based on ret? |
| */ |
| dgap_set_firmware_event(un, UN_LOW | UN_EMPTY); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| return ret; |
| } |
| |
| /* |
| * dgap_tty_write() |
| * |
| * Take data from the user or kernel and send it out to the FEP. |
| * In here exists all the Transparent Print magic as well. |
| */ |
| static int dgap_tty_write(struct tty_struct *tty, const unsigned char *buf, |
| int count) |
| { |
| struct channel_t *ch; |
| struct un_t *un; |
| struct bs_t __iomem *bs; |
| char __iomem *vaddr; |
| u16 head, tail, tmask, remain; |
| int bufcount, n; |
| ulong lock_flags; |
| |
| if (!tty) |
| return 0; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| |
| bs = ch->ch_bs; |
| if (!bs) |
| return 0; |
| |
| if (!count) |
| return 0; |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| /* Get our space available for the channel from the board */ |
| tmask = ch->ch_tsize - 1; |
| head = readw(&(bs->tx_head)) & tmask; |
| tail = readw(&(bs->tx_tail)) & tmask; |
| |
| bufcount = tail - head - 1; |
| if (bufcount < 0) |
| bufcount += ch->ch_tsize; |
| |
| /* |
| * Limit printer output to maxcps overall, with bursts allowed |
| * up to bufsize characters. |
| */ |
| bufcount = dgap_maxcps_room(ch, un, bufcount); |
| |
| /* |
| * Take minimum of what the user wants to send, and the |
| * space available in the FEP buffer. |
| */ |
| count = min(count, bufcount); |
| |
| /* |
| * Bail if no space left. |
| */ |
| if (count <= 0) { |
| dgap_set_firmware_event(un, UN_LOW | UN_EMPTY); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| return 0; |
| } |
| |
| /* |
| * Output the printer ON string, if we are in terminal mode, but |
| * need to be in printer mode. |
| */ |
| if ((un->un_type == DGAP_PRINT) && !(ch->ch_flags & CH_PRON)) { |
| dgap_wmove(ch, ch->ch_digi.digi_onstr, |
| (int) ch->ch_digi.digi_onlen); |
| head = readw(&(bs->tx_head)) & tmask; |
| ch->ch_flags |= CH_PRON; |
| } |
| |
| /* |
| * On the other hand, output the printer OFF string, if we are |
| * currently in printer mode, but need to output to the terminal. |
| */ |
| if ((un->un_type != DGAP_PRINT) && (ch->ch_flags & CH_PRON)) { |
| dgap_wmove(ch, ch->ch_digi.digi_offstr, |
| (int) ch->ch_digi.digi_offlen); |
| head = readw(&(bs->tx_head)) & tmask; |
| ch->ch_flags &= ~CH_PRON; |
| } |
| |
| n = count; |
| |
| /* |
| * 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. |
| */ |
| remain = ch->ch_tstart + ch->ch_tsize - head; |
| |
| if (n >= remain) { |
| n -= remain; |
| vaddr = ch->ch_taddr + head; |
| |
| memcpy_toio(vaddr, (u8 *) buf, remain); |
| |
| head = ch->ch_tstart; |
| buf += remain; |
| } |
| |
| if (n > 0) { |
| |
| /* |
| * Move rest of data. |
| */ |
| vaddr = ch->ch_taddr + head; |
| remain = n; |
| |
| memcpy_toio(vaddr, (u8 *) buf, remain); |
| head += remain; |
| |
| } |
| |
| if (count) { |
| ch->ch_txcount += count; |
| head &= tmask; |
| writew(head, &(bs->tx_head)); |
| } |
| |
| dgap_set_firmware_event(un, UN_LOW | UN_EMPTY); |
| |
| /* |
| * If this is the print device, and the |
| * printer is still on, we need to turn it |
| * off before going idle. If the buffer is |
| * non-empty, wait until it goes empty. |
| * Otherwise turn it off right now. |
| */ |
| if ((un->un_type == DGAP_PRINT) && (ch->ch_flags & CH_PRON)) { |
| tail = readw(&(bs->tx_tail)) & tmask; |
| |
| if (tail != head) { |
| un->un_flags |= UN_EMPTY; |
| writeb(1, &(bs->iempty)); |
| } else { |
| dgap_wmove(ch, ch->ch_digi.digi_offstr, |
| (int) ch->ch_digi.digi_offlen); |
| head = readw(&(bs->tx_head)) & tmask; |
| ch->ch_flags &= ~CH_PRON; |
| } |
| } |
| |
| /* Update printer buffer empty time. */ |
| if ((un->un_type == DGAP_PRINT) && (ch->ch_digi.digi_maxcps > 0) |
| && (ch->ch_digi.digi_bufsize > 0)) { |
| ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps; |
| } |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| return count; |
| } |
| |
| /* |
| * dgap_tty_put_char() |
| * |
| * Put a character into ch->ch_buf |
| * |
| * - used by the line discipline for OPOST processing |
| */ |
| static int dgap_tty_put_char(struct tty_struct *tty, unsigned char c) |
| { |
| /* |
| * Simply call tty_write. |
| */ |
| dgap_tty_write(tty, &c, 1); |
| return 1; |
| } |
| |
| /* |
| * Return modem signals to ld. |
| */ |
| static int dgap_tty_tiocmget(struct tty_struct *tty) |
| { |
| struct channel_t *ch; |
| struct un_t *un; |
| int result; |
| u8 mstat; |
| ulong lock_flags; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return -EIO; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -EIO; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return -EIO; |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| mstat = readb(&(ch->ch_bs->m_stat)); |
| /* Append any outbound signals that might be pending... */ |
| mstat |= ch->ch_mostat; |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| result = 0; |
| |
| if (mstat & D_DTR(ch)) |
| result |= TIOCM_DTR; |
| if (mstat & D_RTS(ch)) |
| result |= TIOCM_RTS; |
| if (mstat & D_CTS(ch)) |
| result |= TIOCM_CTS; |
| if (mstat & D_DSR(ch)) |
| result |= TIOCM_DSR; |
| if (mstat & D_RI(ch)) |
| result |= TIOCM_RI; |
| if (mstat & D_CD(ch)) |
| result |= TIOCM_CD; |
| |
| return result; |
| } |
| |
| /* |
| * dgap_tty_tiocmset() |
| * |
| * Set modem signals, called by ld. |
| */ |
| static int dgap_tty_tiocmset(struct tty_struct *tty, |
| unsigned int set, unsigned int clear) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return -EIO; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -EIO; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return -EIO; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return -EIO; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| if (set & TIOCM_RTS) { |
| ch->ch_mforce |= D_RTS(ch); |
| ch->ch_mval |= D_RTS(ch); |
| } |
| |
| if (set & TIOCM_DTR) { |
| ch->ch_mforce |= D_DTR(ch); |
| ch->ch_mval |= D_DTR(ch); |
| } |
| |
| if (clear & TIOCM_RTS) { |
| ch->ch_mforce |= D_RTS(ch); |
| ch->ch_mval &= ~(D_RTS(ch)); |
| } |
| |
| if (clear & TIOCM_DTR) { |
| ch->ch_mforce |= D_DTR(ch); |
| ch->ch_mval &= ~(D_DTR(ch)); |
| } |
| |
| dgap_param(ch, bd, un->un_type); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_send_break() |
| * |
| * Send a Break, called by ld. |
| */ |
| static int dgap_tty_send_break(struct tty_struct *tty, int msec) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return -EIO; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -EIO; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return -EIO; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return -EIO; |
| |
| switch (msec) { |
| case -1: |
| msec = 0xFFFF; |
| break; |
| case 0: |
| msec = 1; |
| break; |
| default: |
| msec /= 10; |
| break; |
| } |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| #if 0 |
| dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0); |
| #endif |
| dgap_cmdw(ch, SBREAK, (u16) msec, 0); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_wait_until_sent() |
| * |
| * wait until data has been transmitted, called by ld. |
| */ |
| static void dgap_tty_wait_until_sent(struct tty_struct *tty, int timeout) |
| { |
| dgap_wait_for_drain(tty); |
| } |
| |
| /* |
| * dgap_send_xchar() |
| * |
| * send a high priority character, called by ld. |
| */ |
| static void dgap_tty_send_xchar(struct tty_struct *tty, char c) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| /* |
| * This is technically what we should do. |
| * However, the NIST tests specifically want |
| * to see each XON or XOFF character that it |
| * sends, so lets just send each character |
| * by hand... |
| */ |
| #if 0 |
| if (c == STOP_CHAR(tty)) |
| dgap_cmdw(ch, RPAUSE, 0, 0); |
| else if (c == START_CHAR(tty)) |
| dgap_cmdw(ch, RRESUME, 0, 0); |
| else |
| dgap_wmove(ch, &c, 1); |
| #else |
| dgap_wmove(ch, &c, 1); |
| #endif |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| /* |
| * Return modem signals to ld. |
| */ |
| static int dgap_get_modem_info(struct channel_t *ch, unsigned int __user *value) |
| { |
| int result; |
| u8 mstat; |
| ulong lock_flags; |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| mstat = readb(&(ch->ch_bs->m_stat)); |
| /* Append any outbound signals that might be pending... */ |
| mstat |= ch->ch_mostat; |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| result = 0; |
| |
| if (mstat & D_DTR(ch)) |
| result |= TIOCM_DTR; |
| if (mstat & D_RTS(ch)) |
| result |= TIOCM_RTS; |
| if (mstat & D_CTS(ch)) |
| result |= TIOCM_CTS; |
| if (mstat & D_DSR(ch)) |
| result |= TIOCM_DSR; |
| if (mstat & D_RI(ch)) |
| result |= TIOCM_RI; |
| if (mstat & D_CD(ch)) |
| result |= TIOCM_CD; |
| |
| return put_user(result, value); |
| } |
| |
| /* |
| * dgap_set_modem_info() |
| * |
| * Set modem signals, called by ld. |
| */ |
| static int dgap_set_modem_info(struct channel_t *ch, struct board_t *bd, |
| struct un_t *un, unsigned int command, |
| unsigned int __user *value) |
| { |
| int ret; |
| unsigned int arg; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| ret = get_user(arg, value); |
| if (ret) |
| return ret; |
| |
| switch (command) { |
| case TIOCMBIS: |
| if (arg & TIOCM_RTS) { |
| ch->ch_mforce |= D_RTS(ch); |
| ch->ch_mval |= D_RTS(ch); |
| } |
| |
| if (arg & TIOCM_DTR) { |
| ch->ch_mforce |= D_DTR(ch); |
| ch->ch_mval |= D_DTR(ch); |
| } |
| |
| break; |
| |
| case TIOCMBIC: |
| if (arg & TIOCM_RTS) { |
| ch->ch_mforce |= D_RTS(ch); |
| ch->ch_mval &= ~(D_RTS(ch)); |
| } |
| |
| if (arg & TIOCM_DTR) { |
| ch->ch_mforce |= D_DTR(ch); |
| ch->ch_mval &= ~(D_DTR(ch)); |
| } |
| |
| break; |
| |
| case TIOCMSET: |
| ch->ch_mforce = D_DTR(ch)|D_RTS(ch); |
| |
| if (arg & TIOCM_RTS) |
| ch->ch_mval |= D_RTS(ch); |
| else |
| ch->ch_mval &= ~(D_RTS(ch)); |
| |
| if (arg & TIOCM_DTR) |
| ch->ch_mval |= (D_DTR(ch)); |
| else |
| ch->ch_mval &= ~(D_DTR(ch)); |
| |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| dgap_param(ch, bd, un->un_type); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_digigeta() |
| * |
| * Ioctl to get the information for ditty. |
| * |
| * |
| * |
| */ |
| static int dgap_tty_digigeta(struct channel_t *ch, |
| struct digi_t __user *retinfo) |
| { |
| struct digi_t tmp; |
| ulong lock_flags; |
| |
| if (!retinfo) |
| return -EFAULT; |
| |
| memset(&tmp, 0, sizeof(tmp)); |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| memcpy(&tmp, &ch->ch_digi, sizeof(tmp)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_digiseta() |
| * |
| * Ioctl to set the information for ditty. |
| * |
| * |
| * |
| */ |
| static int dgap_tty_digiseta(struct channel_t *ch, struct board_t *bd, |
| struct un_t *un, struct digi_t __user *new_info) |
| { |
| struct digi_t new_digi; |
| ulong lock_flags = 0; |
| unsigned long lock_flags2; |
| |
| if (copy_from_user(&new_digi, new_info, sizeof(struct digi_t))) |
| return -EFAULT; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| memcpy(&ch->ch_digi, &new_digi, sizeof(struct digi_t)); |
| |
| if (ch->ch_digi.digi_maxcps < 1) |
| ch->ch_digi.digi_maxcps = 1; |
| |
| if (ch->ch_digi.digi_maxcps > 10000) |
| ch->ch_digi.digi_maxcps = 10000; |
| |
| if (ch->ch_digi.digi_bufsize < 10) |
| ch->ch_digi.digi_bufsize = 10; |
| |
| if (ch->ch_digi.digi_maxchar < 1) |
| ch->ch_digi.digi_maxchar = 1; |
| |
| if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize) |
| ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize; |
| |
| if (ch->ch_digi.digi_onlen > DIGI_PLEN) |
| ch->ch_digi.digi_onlen = DIGI_PLEN; |
| |
| if (ch->ch_digi.digi_offlen > DIGI_PLEN) |
| ch->ch_digi.digi_offlen = DIGI_PLEN; |
| |
| dgap_param(ch, bd, un->un_type); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_digigetedelay() |
| * |
| * Ioctl to get the current edelay setting. |
| * |
| * |
| * |
| */ |
| static int dgap_tty_digigetedelay(struct tty_struct *tty, int __user *retinfo) |
| { |
| struct channel_t *ch; |
| struct un_t *un; |
| int tmp; |
| ulong lock_flags; |
| |
| if (!retinfo) |
| return -EFAULT; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return -EFAULT; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -EFAULT; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return -EFAULT; |
| |
| memset(&tmp, 0, sizeof(tmp)); |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| tmp = readw(&(ch->ch_bs->edelay)); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_digisetedelay() |
| * |
| * Ioctl to set the EDELAY setting |
| * |
| */ |
| static int dgap_tty_digisetedelay(struct channel_t *ch, struct board_t *bd, |
| struct un_t *un, int __user *new_info) |
| { |
| int new_digi; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (copy_from_user(&new_digi, new_info, sizeof(int))) |
| return -EFAULT; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| writew((u16) new_digi, &(ch->ch_bs->edelay)); |
| |
| dgap_param(ch, bd, un->un_type); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_digigetcustombaud() |
| * |
| * Ioctl to get the current custom baud rate setting. |
| */ |
| static int dgap_tty_digigetcustombaud(struct channel_t *ch, struct un_t *un, |
| int __user *retinfo) |
| { |
| int tmp; |
| ulong lock_flags; |
| |
| if (!retinfo) |
| return -EFAULT; |
| |
| memset(&tmp, 0, sizeof(tmp)); |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| tmp = dgap_get_custom_baud(ch); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_digisetcustombaud() |
| * |
| * Ioctl to set the custom baud rate setting |
| */ |
| static int dgap_tty_digisetcustombaud(struct channel_t *ch, struct board_t *bd, |
| struct un_t *un, int __user *new_info) |
| { |
| uint new_rate; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (copy_from_user(&new_rate, new_info, sizeof(unsigned int))) |
| return -EFAULT; |
| |
| if (bd->bd_flags & BD_FEP5PLUS) { |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| ch->ch_custom_speed = new_rate; |
| |
| dgap_param(ch, bd, un->un_type); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_set_termios() |
| */ |
| static void dgap_tty_set_termios(struct tty_struct *tty, |
| struct ktermios *old_termios) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| unsigned long lock_flags; |
| unsigned long lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| ch->ch_c_cflag = tty->termios.c_cflag; |
| ch->ch_c_iflag = tty->termios.c_iflag; |
| ch->ch_c_oflag = tty->termios.c_oflag; |
| ch->ch_c_lflag = tty->termios.c_lflag; |
| ch->ch_startc = tty->termios.c_cc[VSTART]; |
| ch->ch_stopc = tty->termios.c_cc[VSTOP]; |
| |
| dgap_carrier(ch); |
| dgap_param(ch, bd, un->un_type); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| static void dgap_tty_throttle(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| ch->ch_flags |= (CH_RXBLOCK); |
| #if 1 |
| dgap_cmdw(ch, RPAUSE, 0, 0); |
| #endif |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| } |
| |
| static void dgap_tty_unthrottle(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| ch->ch_flags &= ~(CH_RXBLOCK); |
| |
| #if 1 |
| dgap_cmdw(ch, RRESUME, 0, 0); |
| #endif |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| static struct board_t *find_board_by_major(unsigned int major) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < MAXBOARDS; i++) { |
| struct board_t *brd = dgap_board[i]; |
| |
| if (!brd) |
| return NULL; |
| if (major == brd->serial_driver->major || |
| major == brd->print_driver->major) |
| return brd; |
| } |
| |
| return NULL; |
| } |
| |
| /************************************************************************ |
| * |
| * TTY Entry points and helper functions |
| * |
| ************************************************************************/ |
| |
| /* |
| * dgap_tty_open() |
| * |
| */ |
| static int dgap_tty_open(struct tty_struct *tty, struct file *file) |
| { |
| struct board_t *brd; |
| struct channel_t *ch; |
| struct un_t *un; |
| struct bs_t __iomem *bs; |
| uint major; |
| uint minor; |
| int rc; |
| ulong lock_flags; |
| ulong lock_flags2; |
| u16 head; |
| |
| major = MAJOR(tty_devnum(tty)); |
| minor = MINOR(tty_devnum(tty)); |
| |
| brd = find_board_by_major(major); |
| if (!brd) |
| return -EIO; |
| |
| /* |
| * If board is not yet up to a state of READY, go to |
| * sleep waiting for it to happen or they cancel the open. |
| */ |
| rc = wait_event_interruptible(brd->state_wait, |
| (brd->state & BOARD_READY)); |
| |
| if (rc) |
| return rc; |
| |
| spin_lock_irqsave(&brd->bd_lock, lock_flags); |
| |
| /* The wait above should guarantee this cannot happen */ |
| if (brd->state != BOARD_READY) { |
| spin_unlock_irqrestore(&brd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| /* If opened device is greater than our number of ports, bail. */ |
| if (MINOR(tty_devnum(tty)) > brd->nasync) { |
| spin_unlock_irqrestore(&brd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| ch = brd->channels[minor]; |
| if (!ch) { |
| spin_unlock_irqrestore(&brd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| /* Grab channel lock */ |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| /* Figure out our type */ |
| if (major == brd->serial_driver->major) { |
| un = &brd->channels[minor]->ch_tun; |
| un->un_type = DGAP_SERIAL; |
| } else if (major == brd->print_driver->major) { |
| un = &brd->channels[minor]->ch_pun; |
| un->un_type = DGAP_PRINT; |
| } else { |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&brd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| /* Store our unit into driver_data, so we always have it available. */ |
| tty->driver_data = un; |
| |
| /* |
| * Error if channel info pointer is NULL. |
| */ |
| bs = ch->ch_bs; |
| if (!bs) { |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&brd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| /* |
| * Initialize tty's |
| */ |
| if (!(un->un_flags & UN_ISOPEN)) { |
| /* Store important variables. */ |
| un->un_tty = tty; |
| |
| /* Maybe do something here to the TTY struct as well? */ |
| } |
| |
| /* |
| * Initialize if neither terminal or printer is open. |
| */ |
| if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) { |
| |
| ch->ch_mforce = 0; |
| ch->ch_mval = 0; |
| |
| /* |
| * Flush input queue. |
| */ |
| head = readw(&(bs->rx_head)); |
| writew(head, &(bs->rx_tail)); |
| |
| ch->ch_flags = 0; |
| ch->pscan_state = 0; |
| ch->pscan_savechar = 0; |
| |
| ch->ch_c_cflag = tty->termios.c_cflag; |
| ch->ch_c_iflag = tty->termios.c_iflag; |
| ch->ch_c_oflag = tty->termios.c_oflag; |
| ch->ch_c_lflag = tty->termios.c_lflag; |
| ch->ch_startc = tty->termios.c_cc[VSTART]; |
| ch->ch_stopc = tty->termios.c_cc[VSTOP]; |
| |
| /* TODO: flush our TTY struct here? */ |
| } |
| |
| dgap_carrier(ch); |
| /* |
| * Run param in case we changed anything |
| */ |
| dgap_param(ch, brd, un->un_type); |
| |
| /* |
| * follow protocol for opening port |
| */ |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&brd->bd_lock, lock_flags); |
| |
| rc = dgap_block_til_ready(tty, file, ch); |
| |
| if (!un->un_tty) |
| return -ENODEV; |
| |
| /* No going back now, increment our unit and channel counters */ |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| ch->ch_open_count++; |
| un->un_open_count++; |
| un->un_flags |= (UN_ISOPEN); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| return rc; |
| } |
| |
| /* |
| * dgap_tty_close() |
| * |
| */ |
| static void dgap_tty_close(struct tty_struct *tty, struct file *file) |
| { |
| struct ktermios *ts; |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| ts = &tty->termios; |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| /* |
| * Determine if this is the last close or not - and if we agree about |
| * which type of close it is with the Line Discipline |
| */ |
| if ((tty->count == 1) && (un->un_open_count != 1)) { |
| /* |
| * Uh, oh. tty->count is 1, which means that the tty |
| * structure will be freed. un_open_count should always |
| * be one in these conditions. If it's greater than |
| * one, we've got real problems, since it means the |
| * serial port won't be shutdown. |
| */ |
| un->un_open_count = 1; |
| } |
| |
| if (--un->un_open_count < 0) |
| un->un_open_count = 0; |
| |
| ch->ch_open_count--; |
| |
| if (ch->ch_open_count && un->un_open_count) { |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| return; |
| } |
| |
| /* OK, its the last close on the unit */ |
| |
| un->un_flags |= UN_CLOSING; |
| |
| tty->closing = 1; |
| |
| /* |
| * Only officially close channel if count is 0 and |
| * DIGI_PRINTER bit is not set. |
| */ |
| if ((ch->ch_open_count == 0) && |
| !(ch->ch_digi.digi_flags & DIGI_PRINTER)) { |
| |
| ch->ch_flags &= ~(CH_RXBLOCK); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| |
| /* wait for output to drain */ |
| /* This will also return if we take an interrupt */ |
| |
| dgap_wait_for_drain(tty); |
| |
| dgap_tty_flush_buffer(tty); |
| tty_ldisc_flush(tty); |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| |
| tty->closing = 0; |
| |
| /* |
| * If we have HUPCL set, lower DTR and RTS |
| */ |
| if (ch->ch_c_cflag & HUPCL) { |
| ch->ch_mostat &= ~(D_RTS(ch)|D_DTR(ch)); |
| dgap_cmdb(ch, SMODEM, 0, D_DTR(ch)|D_RTS(ch), 0); |
| |
| /* |
| * Go to sleep to ensure RTS/DTR |
| * have been dropped for modems to see it. |
| */ |
| spin_unlock_irqrestore(&ch->ch_lock, |
| lock_flags); |
| |
| /* .25 second delay for dropping RTS/DTR */ |
| schedule_timeout_interruptible(msecs_to_jiffies(250)); |
| |
| spin_lock_irqsave(&ch->ch_lock, lock_flags); |
| } |
| |
| ch->pscan_state = 0; |
| ch->pscan_savechar = 0; |
| ch->ch_baud_info = 0; |
| |
| } |
| |
| /* |
| * turn off print device when closing print device. |
| */ |
| if ((un->un_type == DGAP_PRINT) && (ch->ch_flags & CH_PRON)) { |
| dgap_wmove(ch, ch->ch_digi.digi_offstr, |
| (int) ch->ch_digi.digi_offlen); |
| ch->ch_flags &= ~CH_PRON; |
| } |
| |
| un->un_tty = NULL; |
| un->un_flags &= ~(UN_ISOPEN | UN_CLOSING); |
| tty->driver_data = NULL; |
| |
| wake_up_interruptible(&ch->ch_flags_wait); |
| wake_up_interruptible(&un->un_flags_wait); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags); |
| } |
| |
| static void dgap_tty_start(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| dgap_cmdw(ch, RESUMETX, 0, 0); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| static void dgap_tty_stop(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| dgap_cmdw(ch, PAUSETX, 0, 0); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| /* |
| * dgap_tty_flush_chars() |
| * |
| * Flush the cook buffer |
| * |
| * Note to self, and any other poor souls who venture here: |
| * |
| * flush in this case DOES NOT mean dispose of the data. |
| * instead, it means "stop buffering and send it if you |
| * haven't already." Just guess how I figured that out... SRW 2-Jun-98 |
| * |
| * It is also always called in interrupt context - JAR 8-Sept-99 |
| */ |
| static void dgap_tty_flush_chars(struct tty_struct *tty) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| ulong lock_flags; |
| ulong lock_flags2; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| /* TODO: Do something here */ |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| } |
| |
| /***************************************************************************** |
| * |
| * The IOCTL function and all of its helpers |
| * |
| *****************************************************************************/ |
| |
| /* |
| * dgap_tty_ioctl() |
| * |
| * The usual assortment of ioctl's |
| */ |
| static int dgap_tty_ioctl(struct tty_struct *tty, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| int rc; |
| u16 head; |
| ulong lock_flags = 0; |
| ulong lock_flags2 = 0; |
| void __user *uarg = (void __user *) arg; |
| |
| if (!tty || tty->magic != TTY_MAGIC) |
| return -ENODEV; |
| |
| un = tty->driver_data; |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return -ENODEV; |
| |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return -ENODEV; |
| |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return -ENODEV; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| if (un->un_open_count <= 0) { |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return -EIO; |
| } |
| |
| switch (cmd) { |
| |
| /* Here are all the standard ioctl's that we MUST implement */ |
| |
| case TCSBRK: |
| /* |
| * TCSBRK is SVID version: non-zero arg --> no break |
| * this behaviour is exploited by tcdrain(). |
| * |
| * According to POSIX.1 spec (7.2.2.1.2) breaks should be |
| * between 0.25 and 0.5 seconds so we'll ask for something |
| * in the middle: 0.375 seconds. |
| */ |
| rc = tty_check_change(tty); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| if (rc) |
| return rc; |
| |
| rc = dgap_wait_for_drain(tty); |
| |
| if (rc) |
| return -EINTR; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP)) |
| dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| |
| case TCSBRKP: |
| /* support for POSIX tcsendbreak() |
| |
| * According to POSIX.1 spec (7.2.2.1.2) breaks should be |
| * between 0.25 and 0.5 seconds so we'll ask for something |
| * in the middle: 0.375 seconds. |
| */ |
| rc = tty_check_change(tty); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| if (rc) |
| return rc; |
| |
| rc = dgap_wait_for_drain(tty); |
| if (rc) |
| return -EINTR; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| |
| case TIOCSBRK: |
| /* |
| * FEP5 doesn't support turning on a break unconditionally. |
| * The FEP5 device will stop sending a break automatically |
| * after the specified time value that was sent when turning on |
| * the break. |
| */ |
| rc = tty_check_change(tty); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| if (rc) |
| return rc; |
| |
| rc = dgap_wait_for_drain(tty); |
| if (rc) |
| return -EINTR; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| |
| dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0); |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| |
| case TIOCCBRK: |
| /* |
| * FEP5 doesn't support turning off a break unconditionally. |
| * The FEP5 device will stop sending a break automatically |
| * after the specified time value that was sent when turning on |
| * the break. |
| */ |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return 0; |
| |
| case TIOCGSOFTCAR: |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| rc = put_user(C_CLOCAL(tty) ? 1 : 0, |
| (unsigned long __user *) arg); |
| return rc; |
| |
| case TIOCSSOFTCAR: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| rc = get_user(arg, (unsigned long __user *) arg); |
| if (rc) |
| return rc; |
| |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | |
| (arg ? CLOCAL : 0)); |
| dgap_param(ch, bd, un->un_type); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return 0; |
| |
| case TIOCMGET: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_get_modem_info(ch, uarg); |
| |
| case TIOCMBIS: |
| case TIOCMBIC: |
| case TIOCMSET: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_set_modem_info(ch, bd, un, cmd, uarg); |
| |
| /* |
| * Here are any additional ioctl's that we want to implement |
| */ |
| |
| case TCFLSH: |
| /* |
| * The linux tty driver doesn't have a flush |
| * input routine for the driver, assuming all backed |
| * up data is in the line disc. buffers. However, |
| * we all know that's not the case. Here, we |
| * act on the ioctl, but then lie and say we didn't |
| * so the line discipline will process the flush |
| * also. |
| */ |
| rc = tty_check_change(tty); |
| if (rc) { |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return rc; |
| } |
| |
| if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) { |
| if (!(un->un_type == DGAP_PRINT)) { |
| head = readw(&(ch->ch_bs->rx_head)); |
| writew(head, &(ch->ch_bs->rx_tail)); |
| writeb(0, &(ch->ch_bs->orun)); |
| } |
| } |
| |
| if ((arg != TCOFLUSH) && (arg != TCIOFLUSH)) { |
| /* pretend we didn't recognize this IOCTL */ |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return -ENOIOCTLCMD; |
| } |
| |
| ch->ch_flags &= ~CH_STOP; |
| head = readw(&(ch->ch_bs->tx_head)); |
| dgap_cmdw(ch, FLUSHTX, (u16) head, 0); |
| dgap_cmdw(ch, RESUMETX, 0, 0); |
| if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) { |
| ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY); |
| wake_up_interruptible(&ch->ch_tun.un_flags_wait); |
| } |
| if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) { |
| ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY); |
| wake_up_interruptible(&ch->ch_pun.un_flags_wait); |
| } |
| if (waitqueue_active(&tty->write_wait)) |
| wake_up_interruptible(&tty->write_wait); |
| |
| /* Can't hold any locks when calling tty_wakeup! */ |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| tty_wakeup(tty); |
| |
| /* pretend we didn't recognize this IOCTL */ |
| return -ENOIOCTLCMD; |
| |
| case TCSETSF: |
| case TCSETSW: |
| /* |
| * The linux tty driver doesn't have a flush |
| * input routine for the driver, assuming all backed |
| * up data is in the line disc. buffers. However, |
| * we all know that's not the case. Here, we |
| * act on the ioctl, but then lie and say we didn't |
| * so the line discipline will process the flush |
| * also. |
| */ |
| if (cmd == TCSETSF) { |
| /* flush rx */ |
| ch->ch_flags &= ~CH_STOP; |
| head = readw(&(ch->ch_bs->rx_head)); |
| writew(head, &(ch->ch_bs->rx_tail)); |
| } |
| |
| /* now wait for all the output to drain */ |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| rc = dgap_wait_for_drain(tty); |
| if (rc) |
| return -EINTR; |
| |
| /* pretend we didn't recognize this */ |
| return -ENOIOCTLCMD; |
| |
| case TCSETAW: |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| rc = dgap_wait_for_drain(tty); |
| if (rc) |
| return -EINTR; |
| |
| /* pretend we didn't recognize this */ |
| return -ENOIOCTLCMD; |
| |
| case TCXONC: |
| /* |
| * The Linux Line Discipline (LD) would do this for us if we |
| * let it, but we have the special firmware options to do this |
| * the "right way" regardless of hardware or software flow |
| * control so we'll do it outselves instead of letting the LD |
| * do it. |
| */ |
| rc = tty_check_change(tty); |
| if (rc) { |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return rc; |
| } |
| |
| switch (arg) { |
| |
| case TCOON: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| dgap_tty_start(tty); |
| return 0; |
| case TCOOFF: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| dgap_tty_stop(tty); |
| return 0; |
| case TCION: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| /* Make the ld do it */ |
| return -ENOIOCTLCMD; |
| case TCIOFF: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| /* Make the ld do it */ |
| return -ENOIOCTLCMD; |
| default: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return -EINVAL; |
| } |
| |
| case DIGI_GETA: |
| /* get information for ditty */ |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_tty_digigeta(ch, uarg); |
| |
| case DIGI_SETAW: |
| case DIGI_SETAF: |
| |
| /* set information for ditty */ |
| if (cmd == (DIGI_SETAW)) { |
| |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| rc = dgap_wait_for_drain(tty); |
| if (rc) |
| return -EINTR; |
| spin_lock_irqsave(&bd->bd_lock, lock_flags); |
| spin_lock_irqsave(&ch->ch_lock, lock_flags2); |
| } else |
| tty_ldisc_flush(tty); |
| /* fall thru */ |
| |
| case DIGI_SETA: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_tty_digiseta(ch, bd, un, uarg); |
| |
| case DIGI_GEDELAY: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_tty_digigetedelay(tty, uarg); |
| |
| case DIGI_SEDELAY: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_tty_digisetedelay(ch, bd, un, uarg); |
| |
| case DIGI_GETCUSTOMBAUD: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_tty_digigetcustombaud(ch, un, uarg); |
| |
| case DIGI_SETCUSTOMBAUD: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return dgap_tty_digisetcustombaud(ch, bd, un, uarg); |
| |
| case DIGI_RESET_PORT: |
| dgap_firmware_reset_port(ch); |
| dgap_param(ch, bd, un->un_type); |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| return 0; |
| |
| default: |
| spin_unlock_irqrestore(&ch->ch_lock, lock_flags2); |
| spin_unlock_irqrestore(&bd->bd_lock, lock_flags); |
| |
| return -ENOIOCTLCMD; |
| } |
| } |
| |
| static const struct tty_operations dgap_tty_ops = { |
| .open = dgap_tty_open, |
| .close = dgap_tty_close, |
| .write = dgap_tty_write, |
| .write_room = dgap_tty_write_room, |
| .flush_buffer = dgap_tty_flush_buffer, |
| .chars_in_buffer = dgap_tty_chars_in_buffer, |
| .flush_chars = dgap_tty_flush_chars, |
| .ioctl = dgap_tty_ioctl, |
| .set_termios = dgap_tty_set_termios, |
| .stop = dgap_tty_stop, |
| .start = dgap_tty_start, |
| .throttle = dgap_tty_throttle, |
| .unthrottle = dgap_tty_unthrottle, |
| .hangup = dgap_tty_hangup, |
| .put_char = dgap_tty_put_char, |
| .tiocmget = dgap_tty_tiocmget, |
| .tiocmset = dgap_tty_tiocmset, |
| .break_ctl = dgap_tty_send_break, |
| .wait_until_sent = dgap_tty_wait_until_sent, |
| .send_xchar = dgap_tty_send_xchar |
| }; |
| |
| /************************************************************************ |
| * |
| * TTY Initialization/Cleanup Functions |
| * |
| ************************************************************************/ |
| |
| /* |
| * dgap_tty_register() |
| * |
| * Init the tty subsystem for this board. |
| */ |
| static int dgap_tty_register(struct board_t *brd) |
| { |
| int rc; |
| |
| brd->serial_driver = tty_alloc_driver(MAXPORTS, |
| TTY_DRIVER_REAL_RAW | |
| TTY_DRIVER_DYNAMIC_DEV | |
| TTY_DRIVER_HARDWARE_BREAK); |
| if (IS_ERR(brd->serial_driver)) |
| return PTR_ERR(brd->serial_driver); |
| |
| snprintf(brd->serial_name, MAXTTYNAMELEN, "tty_dgap_%d_", |
| brd->boardnum); |
| brd->serial_driver->name = brd->serial_name; |
| brd->serial_driver->name_base = 0; |
| brd->serial_driver->major = 0; |
| brd->serial_driver->minor_start = 0; |
| brd->serial_driver->type = TTY_DRIVER_TYPE_SERIAL; |
| brd->serial_driver->subtype = SERIAL_TYPE_NORMAL; |
| brd->serial_driver->init_termios = dgap_default_termios; |
| brd->serial_driver->driver_name = DRVSTR; |
| |
| /* |
| * Entry points for driver. Called by the kernel from |
| * tty_io.c and n_tty.c. |
| */ |
| tty_set_operations(brd->serial_driver, &dgap_tty_ops); |
| |
| /* |
| * If we're doing transparent print, we have to do all of the above |
| * again, separately so we don't get the LD confused about what major |
| * we are when we get into the dgap_tty_open() routine. |
| */ |
| brd->print_driver = tty_alloc_driver(MAXPORTS, |
| TTY_DRIVER_REAL_RAW | |
| TTY_DRIVER_DYNAMIC_DEV | |
| TTY_DRIVER_HARDWARE_BREAK); |
| if (IS_ERR(brd->print_driver)) { |
| rc = PTR_ERR(brd->print_driver); |
| goto free_serial_drv; |
| } |
| |
| snprintf(brd->print_name, MAXTTYNAMELEN, "pr_dgap_%d_", |
| brd->boardnum); |
| brd->print_driver->name = brd->print_name; |
| brd->print_driver->name_base = 0; |
| brd->print_driver->major = 0; |
| brd->print_driver->minor_start = 0; |
| brd->print_driver->type = TTY_DRIVER_TYPE_SERIAL; |
| brd->print_driver->subtype = SERIAL_TYPE_NORMAL; |
| brd->print_driver->init_termios = dgap_default_termios; |
| brd->print_driver->driver_name = DRVSTR; |
| |
| /* |
| * Entry points for driver. Called by the kernel from |
| * tty_io.c and n_tty.c. |
| */ |
| tty_set_operations(brd->print_driver, &dgap_tty_ops); |
| |
| /* Register tty devices */ |
| rc = tty_register_driver(brd->serial_driver); |
| if (rc < 0) |
| goto free_print_drv; |
| |
| /* Register Transparent Print devices */ |
| rc = tty_register_driver(brd->print_driver); |
| if (rc < 0) |
| goto unregister_serial_drv; |
| |
| return 0; |
| |
| unregister_serial_drv: |
| tty_unregister_driver(brd->serial_driver); |
| free_print_drv: |
| put_tty_driver(brd->print_driver); |
| free_serial_drv: |
| put_tty_driver(brd->serial_driver); |
| |
| return rc; |
| } |
| |
| static void dgap_tty_unregister(struct board_t *brd) |
| { |
| tty_unregister_driver(brd->print_driver); |
| tty_unregister_driver(brd->serial_driver); |
| put_tty_driver(brd->print_driver); |
| put_tty_driver(brd->serial_driver); |
| } |
| |
| static int dgap_alloc_flipbuf(struct board_t *brd) |
| { |
| /* |
| * allocate flip buffer for board. |
| */ |
| brd->flipbuf = kmalloc(MYFLIPLEN, GFP_KERNEL); |
| if (!brd->flipbuf) |
| return -ENOMEM; |
| |
| brd->flipflagbuf = kmalloc(MYFLIPLEN, GFP_KERNEL); |
| if (!brd->flipflagbuf) { |
| kfree(brd->flipbuf); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static void dgap_free_flipbuf(struct board_t *brd) |
| { |
| kfree(brd->flipbuf); |
| kfree(brd->flipflagbuf); |
| } |
| |
| static struct board_t *dgap_verify_board(struct device *p) |
| { |
| struct board_t *bd; |
| |
| if (!p) |
| return NULL; |
| |
| bd = dev_get_drvdata(p); |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC || bd->state != BOARD_READY) |
| return NULL; |
| |
| return bd; |
| } |
| |
| static ssize_t dgap_ports_state_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) { |
| count += snprintf(buf + count, PAGE_SIZE - count, |
| "%d %s\n", bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_open_count ? "Open" : "Closed"); |
| } |
| return count; |
| } |
| static DEVICE_ATTR(ports_state, S_IRUSR, dgap_ports_state_show, NULL); |
| |
| static ssize_t dgap_ports_baud_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) { |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %d\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_baud_info); |
| } |
| return count; |
| } |
| static DEVICE_ATTR(ports_baud, S_IRUSR, dgap_ports_baud_show, NULL); |
| |
| static ssize_t dgap_ports_msignals_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) { |
| if (bd->channels[i]->ch_open_count) |
| count += snprintf(buf + count, PAGE_SIZE - count, |
| "%d %s %s %s %s %s %s\n", |
| bd->channels[i]->ch_portnum, |
| (bd->channels[i]->ch_mostat & |
| UART_MCR_RTS) ? "RTS" : "", |
| (bd->channels[i]->ch_mistat & |
| UART_MSR_CTS) ? "CTS" : "", |
| (bd->channels[i]->ch_mostat & |
| UART_MCR_DTR) ? "DTR" : "", |
| (bd->channels[i]->ch_mistat & |
| UART_MSR_DSR) ? "DSR" : "", |
| (bd->channels[i]->ch_mistat & |
| UART_MSR_DCD) ? "DCD" : "", |
| (bd->channels[i]->ch_mistat & |
| UART_MSR_RI) ? "RI" : ""); |
| else |
| count += snprintf(buf + count, PAGE_SIZE - count, |
| "%d\n", bd->channels[i]->ch_portnum); |
| } |
| return count; |
| } |
| static DEVICE_ATTR(ports_msignals, S_IRUSR, dgap_ports_msignals_show, NULL); |
| |
| static ssize_t dgap_ports_iflag_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_c_iflag); |
| return count; |
| } |
| static DEVICE_ATTR(ports_iflag, S_IRUSR, dgap_ports_iflag_show, NULL); |
| |
| static ssize_t dgap_ports_cflag_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_c_cflag); |
| return count; |
| } |
| static DEVICE_ATTR(ports_cflag, S_IRUSR, dgap_ports_cflag_show, NULL); |
| |
| static ssize_t dgap_ports_oflag_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_c_oflag); |
| return count; |
| } |
| static DEVICE_ATTR(ports_oflag, S_IRUSR, dgap_ports_oflag_show, NULL); |
| |
| static ssize_t dgap_ports_lflag_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_c_lflag); |
| return count; |
| } |
| static DEVICE_ATTR(ports_lflag, S_IRUSR, dgap_ports_lflag_show, NULL); |
| |
| static ssize_t dgap_ports_digi_flag_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_digi.digi_flags); |
| return count; |
| } |
| static DEVICE_ATTR(ports_digi_flag, S_IRUSR, dgap_ports_digi_flag_show, NULL); |
| |
| static ssize_t dgap_ports_rxcount_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_rxcount); |
| return count; |
| } |
| static DEVICE_ATTR(ports_rxcount, S_IRUSR, dgap_ports_rxcount_show, NULL); |
| |
| static ssize_t dgap_ports_txcount_show(struct device *p, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| int count = 0; |
| unsigned int i; |
| |
| bd = dgap_verify_board(p); |
| if (!bd) |
| return 0; |
| |
| for (i = 0; i < bd->nasync; i++) |
| count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n", |
| bd->channels[i]->ch_portnum, |
| bd->channels[i]->ch_txcount); |
| return count; |
| } |
| static DEVICE_ATTR(ports_txcount, S_IRUSR, dgap_ports_txcount_show, NULL); |
| |
| static ssize_t dgap_tty_state_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%s", un->un_open_count ? |
| "Open" : "Closed"); |
| } |
| static DEVICE_ATTR(state, S_IRUSR, dgap_tty_state_show, NULL); |
| |
| static ssize_t dgap_tty_baud_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_baud_info); |
| } |
| static DEVICE_ATTR(baud, S_IRUSR, dgap_tty_baud_show, NULL); |
| |
| static ssize_t dgap_tty_msignals_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| if (ch->ch_open_count) { |
| return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n", |
| (ch->ch_mostat & UART_MCR_RTS) ? "RTS" : "", |
| (ch->ch_mistat & UART_MSR_CTS) ? "CTS" : "", |
| (ch->ch_mostat & UART_MCR_DTR) ? "DTR" : "", |
| (ch->ch_mistat & UART_MSR_DSR) ? "DSR" : "", |
| (ch->ch_mistat & UART_MSR_DCD) ? "DCD" : "", |
| (ch->ch_mistat & UART_MSR_RI) ? "RI" : ""); |
| } |
| return 0; |
| } |
| static DEVICE_ATTR(msignals, S_IRUSR, dgap_tty_msignals_show, NULL); |
| |
| static ssize_t dgap_tty_iflag_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_iflag); |
| } |
| static DEVICE_ATTR(iflag, S_IRUSR, dgap_tty_iflag_show, NULL); |
| |
| static ssize_t dgap_tty_cflag_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_cflag); |
| } |
| static DEVICE_ATTR(cflag, S_IRUSR, dgap_tty_cflag_show, NULL); |
| |
| static ssize_t dgap_tty_oflag_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_oflag); |
| } |
| static DEVICE_ATTR(oflag, S_IRUSR, dgap_tty_oflag_show, NULL); |
| |
| static ssize_t dgap_tty_lflag_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_lflag); |
| } |
| static DEVICE_ATTR(lflag, S_IRUSR, dgap_tty_lflag_show, NULL); |
| |
| static ssize_t dgap_tty_digi_flag_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags); |
| } |
| static DEVICE_ATTR(digi_flag, S_IRUSR, dgap_tty_digi_flag_show, NULL); |
| |
| static ssize_t dgap_tty_rxcount_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_rxcount); |
| } |
| static DEVICE_ATTR(rxcount, S_IRUSR, dgap_tty_rxcount_show, NULL); |
| |
| static ssize_t dgap_tty_txcount_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_txcount); |
| } |
| static DEVICE_ATTR(txcount, S_IRUSR, dgap_tty_txcount_show, NULL); |
| |
| static ssize_t dgap_tty_name_show(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct board_t *bd; |
| struct channel_t *ch; |
| struct un_t *un; |
| int cn; |
| int bn; |
| struct cnode *cptr; |
| int found = FALSE; |
| int ncount = 0; |
| int starto = 0; |
| int i; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un || un->magic != DGAP_UNIT_MAGIC) |
| return 0; |
| ch = un->un_ch; |
| if (!ch || ch->magic != DGAP_CHANNEL_MAGIC) |
| return 0; |
| bd = ch->ch_bd; |
| if (!bd || bd->magic != DGAP_BOARD_MAGIC) |
| return 0; |
| if (bd->state != BOARD_READY) |
| return 0; |
| |
| bn = bd->boardnum; |
| cn = ch->ch_portnum; |
| |
| for (cptr = bd->bd_config; cptr; cptr = cptr->next) { |
| |
| if ((cptr->type == BNODE) && |
| ((cptr->u.board.type == APORT2_920P) || |
| (cptr->u.board.type == APORT4_920P) || |
| (cptr->u.board.type == APORT8_920P) || |
| (cptr->u.board.type == PAPORT4) || |
| (cptr->u.board.type == PAPORT8))) { |
| |
| found = TRUE; |
| if (cptr->u.board.v_start) |
| starto = cptr->u.board.start; |
| else |
| starto = 1; |
| } |
| |
| if (cptr->type == TNODE && found == TRUE) { |
| char *ptr1; |
| |
| if (strstr(cptr->u.ttyname, "tty")) { |
| ptr1 = cptr->u.ttyname; |
| ptr1 += 3; |
| } else |
| ptr1 = cptr->u.ttyname; |
| |
| for (i = 0; i < dgap_config_get_num_prts(bd); i++) { |
| if (cn != i) |
| continue; |
| |
| return snprintf(buf, PAGE_SIZE, "%s%s%02d\n", |
| (un->un_type == DGAP_PRINT) ? |
| "pr" : "tty", |
| ptr1, i + starto); |
| } |
| } |
| |
| if (cptr->type == CNODE) { |
| |
| for (i = 0; i < cptr->u.conc.nport; i++) { |
| if (cn != (i + ncount)) |
| continue; |
| |
| return snprintf(buf, PAGE_SIZE, "%s%s%02ld\n", |
| (un->un_type == DGAP_PRINT) ? |
| "pr" : "tty", |
| cptr->u.conc.id, |
| i + (cptr->u.conc.v_start ? |
| cptr->u.conc.start : 1)); |
| } |
| |
| ncount += cptr->u.conc.nport; |
| } |
| |
| if (cptr->type == MNODE) { |
| |
| for (i = 0; i < cptr->u.module.nport; i++) { |
| if (cn != (i + ncount)) |
| continue; |
| |
| return snprintf(buf, PAGE_SIZE, "%s%s%02ld\n", |
| (un->un_type == DGAP_PRINT) ? |
| "pr" : "tty", |
| cptr->u.module.id, |
| i + (cptr->u.module.v_start ? |
| cptr->u.module.start : 1)); |
| } |
| |
| ncount += cptr->u.module.nport; |
| } |
| } |
| |
| return snprintf(buf, PAGE_SIZE, "%s_dgap_%d_%d\n", |
| (un->un_type == DGAP_PRINT) ? "pr" : "tty", bn, cn); |
| } |
| static DEVICE_ATTR(custom_name, S_IRUSR, dgap_tty_name_show, NULL); |
| |
| static struct attribute *dgap_sysfs_tty_entries[] = { |
| &dev_attr_state.attr, |
| &dev_attr_baud.attr, |
| &dev_attr_msignals.attr, |
| &dev_attr_iflag.attr, |
| &dev_attr_cflag.attr, |
| &dev_attr_oflag.attr, |
| &dev_attr_lflag.attr, |
| &dev_attr_digi_flag.attr, |
| &dev_attr_rxcount.attr, |
| &dev_attr_txcount.attr, |
| &dev_attr_custom_name.attr, |
| NULL |
| }; |
| |
| |
| /* this function creates the sys files that will export each signal status |
| * to sysfs each value will be put in a separate filename |
| */ |
| static void dgap_create_ports_sysfiles(struct board_t *bd) |
| { |
| dev_set_drvdata(&bd->pdev->dev, bd); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_state); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_baud); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_msignals); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_iflag); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_cflag); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_oflag); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_lflag); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_rxcount); |
| device_create_file(&(bd->pdev->dev), &dev_attr_ports_txcount); |
| } |
| |
| /* removes all the sys files created for that port */ |
| static void dgap_remove_ports_sysfiles(struct board_t *bd) |
| { |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_state); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_baud); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_msignals); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_iflag); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_cflag); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_oflag); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_lflag); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_rxcount); |
| device_remove_file(&(bd->pdev->dev), &dev_attr_ports_txcount); |
| } |
| |
| /* |
| * Copies the BIOS code from the user to the board, |
| * and starts the BIOS running. |
| */ |
| static void dgap_do_bios_load(struct board_t *brd, const u8 *ubios, int len) |
| { |
| u8 __iomem *addr; |
| uint offset; |
| unsigned int i; |
| |
| if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) |
| return; |
| |
| addr = brd->re_map_membase; |
| |
| /* |
| * clear POST area |
| */ |
| for (i = 0; i < 16; i++) |
| writeb(0, addr + POSTAREA + i); |
| |
| /* |
| * Download bios |
| */ |
| offset = 0x1000; |
| memcpy_toio(addr + offset, ubios, len); |
| |
| writel(0x0bf00401, addr); |
| writel(0, (addr + 4)); |
| |
| /* Clear the reset, and change states. */ |
| writeb(FEPCLR, brd->re_map_port); |
| } |
| |
| /* |
| * Checks to see if the BIOS completed running on the card. |
| */ |
| static int dgap_test_bios(struct board_t *brd) |
| { |
| u8 __iomem *addr; |
| u16 word; |
| u16 err1; |
| u16 err2; |
| |
| if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) |
| return -EINVAL; |
| |
| addr = brd->re_map_membase; |
| word = readw(addr + POSTAREA); |
| |
| /* |
| * It can take 5-6 seconds for a board to |
| * pass the bios self test and post results. |
| * Give it 10 seconds. |
| */ |
| brd->wait_for_bios = 0; |
| while (brd->wait_for_bios < 1000) { |
| /* Check to see if BIOS thinks board is good. (GD). */ |
| if (word == *(u16 *) "GD") |
| return 0; |
| msleep_interruptible(10); |
| brd->wait_for_bios++; |
| word = readw(addr + POSTAREA); |
| } |
| |
| /* Gave up on board after too long of time taken */ |
| err1 = readw(addr + SEQUENCE); |
| err2 = readw(addr + ERROR); |
| dev_warn(&brd->pdev->dev, "%s failed diagnostics. Error #(%x,%x).\n", |
| brd->name, err1, err2); |
| brd->state = BOARD_FAILED; |
| brd->dpastatus = BD_NOBIOS; |
| |
| return -EIO; |
| } |
| |
| /* |
| * Copies the FEP code from the user to the board, |
| * and starts the FEP running. |
| */ |
| static void dgap_do_fep_load(struct board_t *brd, const u8 *ufep, int len) |
| { |
| u8 __iomem *addr; |
| uint offset; |
| |
| if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) |
| return; |
| |
| addr = brd->re_map_membase; |
| |
| /* |
| * Download FEP |
| */ |
| offset = 0x1000; |
| memcpy_toio(addr + offset, ufep, len); |
| |
| /* |
| * 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)) { |
| u8 string[100]; |
| u8 __iomem *config; |
| u8 *xconfig; |
| unsigned 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)); |
| |
| } |
| |
| /* |
| * Waits for the FEP to report thats its ready for us to use. |
| */ |
| static int dgap_test_fep(struct board_t *brd) |
| { |
| u8 __iomem *addr; |
| u16 word; |
| u16 err1; |
| u16 err2; |
| |
| if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase) |
| return -EINVAL; |
| |
| addr = brd->re_map_membase; |
| word = readw(addr + FEPSTAT); |
| |
| /* |
| * It can take 2-3 seconds for the FEP to |
| * be up and running. Give it 5 secs. |
| */ |
| brd->wait_for_fep = 0; |
| while (brd->wait_for_fep < 500) { |
| /* Check to see if FEP is up and running now. */ |
| if (word == *(u16 *) "OS") { |
| /* |
| * Check to see if the board can support FEP5+ commands. |
| */ |
| word = readw(addr + FEP5_PLUS); |
| if (word == *(u16 *) "5A") |
| brd->bd_flags |= BD_FEP5PLUS; |
| |
| return 0; |
| } |
| msleep_interruptible(10); |
| brd->wait_for_fep++; |
| word = readw(addr + FEPSTAT); |
| } |
| |
| /* Gave up on board after too long of time taken */ |
| err1 = readw(addr + SEQUENCE); |
| err2 = readw(addr + ERROR); |
| dev_warn(&brd->pdev->dev, |
| "FEPOS for %s not functioning. Error #(%x,%x).\n", |
| brd->name, err1, err2); |
| brd->state = BOARD_FAILED; |
| brd->dpastatus = BD_NOFEP; |
| |
| return -EIO; |
| } |
| |
| /* |
| * Physically forces the FEP5 card to reset itself. |
| */ |
| static void dgap_do_reset_board(struct board_t *brd) |
| { |
| u8 check; |
| u32 check1; |
| u32 check2; |
| unsigned int i; |
| |
| if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || |
| !brd->re_map_membase || !brd->re_map_port) |
| return; |
| |
| /* 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) { |
| dev_warn(&brd->pdev->dev, |
| "dgap: Board not resetting... Failing board.\n"); |
| brd->state = BOARD_FAILED; |
| brd->dpastatus = BD_NOFEP; |
| return; |
| } |
| |
| /* |
| * 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)) { |
| dev_warn(&brd->pdev->dev, |
| "No memory at %p for board.\n", |
| brd->re_map_membase); |
| brd->state = BOARD_FAILED; |
| brd->dpastatus = BD_NOFEP; |
| return; |
| } |
| } |
| |
| #ifdef DIGI_CONCENTRATORS_SUPPORTED |
| /* |
| * Sends a concentrator image into the FEP5 board. |
| */ |
| static void dgap_do_conc_load(struct board_t *brd, u8 *uaddr, int len) |
| { |
| char __iomem *vaddr; |
| u16 offset; |
| 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); |
| memcpy_toio(to_dp, uaddr, len); |
| |
| /* Tell card we have data for it */ |
| writew(0, vaddr + (DOWNREQ)); |
| |
| brd->conc_dl_status = NO_PENDING_CONCENTRATOR_REQUESTS; |
| } |
| #endif |
| |
| #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; |
| u8 byte1; |
| u8 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); |
| } |
| |
| |
| static ssize_t dgap_driver_version_show(struct device_driver *ddp, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%s\n", DG_PART); |
| } |
| static DRIVER_ATTR(version, S_IRUSR, dgap_driver_version_show, NULL); |
| |
| |
| static ssize_t dgap_driver_boards_show(struct device_driver *ddp, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", dgap_numboards); |
| } |
| static DRIVER_ATTR(boards, S_IRUSR, dgap_driver_boards_show, NULL); |
| |
| |
| static ssize_t dgap_driver_maxboards_show(struct device_driver *ddp, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", MAXBOARDS); |
| } |
| static DRIVER_ATTR(maxboards, S_IRUSR, dgap_driver_maxboards_show, NULL); |
| |
| |
| static ssize_t dgap_driver_pollcounter_show(struct device_driver *ddp, |
| char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%ld\n", dgap_poll_counter); |
| } |
| static DRIVER_ATTR(pollcounter, S_IRUSR, dgap_driver_pollcounter_show, NULL); |
| |
| static ssize_t dgap_driver_pollrate_show(struct device_driver *ddp, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%dms\n", dgap_poll_tick); |
| } |
| |
| static ssize_t dgap_driver_pollrate_store(struct device_driver *ddp, |
| const char *buf, size_t count) |
| { |
| if (sscanf(buf, "%d\n", &dgap_poll_tick) != 1) |
| return -EINVAL; |
| return count; |
| } |
| static DRIVER_ATTR(pollrate, (S_IRUSR | S_IWUSR), dgap_driver_pollrate_show, |
| dgap_driver_pollrate_store); |
| |
| |
| static int dgap_create_driver_sysfiles(struct pci_driver *dgap_driver) |
| { |
| int rc = 0; |
| struct device_driver *driverfs = &dgap_driver->driver; |
| |
| rc |= driver_create_file(driverfs, &driver_attr_version); |
| rc |= driver_create_file(driverfs, &driver_attr_boards); |
| rc |= driver_create_file(driverfs, &driver_attr_maxboards); |
| rc |= driver_create_file(driverfs, &driver_attr_pollrate); |
| rc |= driver_create_file(driverfs, &driver_attr_pollcounter); |
| |
| return rc; |
| } |
| |
| static void dgap_remove_driver_sysfiles(struct pci_driver *dgap_driver) |
| { |
| struct device_driver *driverfs = &dgap_driver->driver; |
| |
| driver_remove_file(driverfs, &driver_attr_version); |
| driver_remove_file(driverfs, &driver_attr_boards); |
| driver_remove_file(driverfs, &driver_attr_maxboards); |
| driver_remove_file(driverfs, &driver_attr_pollrate); |
| driver_remove_file(driverfs, &driver_attr_pollcounter); |
| } |
| |
| static struct attribute_group dgap_tty_attribute_group = { |
| .name = NULL, |
| .attrs = dgap_sysfs_tty_entries, |
| }; |
| |
| static void dgap_create_tty_sysfs(struct un_t *un, struct device *c) |
| { |
| int ret; |
| |
| ret = sysfs_create_group(&c->kobj, &dgap_tty_attribute_group); |
| if (ret) |
| return; |
| |
| dev_set_drvdata(c, un); |
| |
| } |
| |
| static void dgap_remove_tty_sysfs(struct device *c) |
| { |
| sysfs_remove_group(&c->kobj, &dgap_tty_attribute_group); |
| } |
| |
| /* |
| * Create pr and tty device entries |
| */ |
| static int dgap_tty_register_ports(struct board_t *brd) |
| { |
| struct channel_t *ch; |
| int i; |
| int ret; |
| |
| brd->serial_ports = kcalloc(brd->nasync, sizeof(*brd->serial_ports), |
| GFP_KERNEL); |
| if (!brd->serial_ports) |
| return -ENOMEM; |
| |
| brd->printer_ports = kcalloc(brd->nasync, sizeof(*brd->printer_ports), |
| GFP_KERNEL); |
| if (!brd->printer_ports) { |
| ret = -ENOMEM; |
| goto free_serial_ports; |
| } |
| |
| for (i = 0; i < brd->nasync; i++) { |
| tty_port_init(&brd->serial_ports[i]); |
| tty_port_init(&brd->printer_ports[i]); |
| } |
| |
| ch = brd->channels[0]; |
| for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) { |
| |
| struct device *classp; |
| |
| classp = tty_port_register_device(&brd->serial_ports[i], |
| brd->serial_driver, |
| i, NULL); |
| |
| if (IS_ERR(classp)) { |
| ret = PTR_ERR(classp); |
| goto unregister_ttys; |
| } |
| |
| dgap_create_tty_sysfs(&ch->ch_tun, classp); |
| ch->ch_tun.un_sysfs = classp; |
| |
| classp = tty_port_register_device(&brd->printer_ports[i], |
| brd->print_driver, |
| i, NULL); |
| |
| if (IS_ERR(classp)) { |
| ret = PTR_ERR(classp); |
| goto unregister_ttys; |
| } |
| |
| dgap_create_tty_sysfs(&ch->ch_pun, classp); |
| ch->ch_pun.un_sysfs = classp; |
| } |
| dgap_create_ports_sysfiles(brd); |
| |
| return 0; |
| |
| unregister_ttys: |
| while (i >= 0) { |
| ch = brd->channels[i]; |
| if (ch->ch_tun.un_sysfs) { |
| dgap_remove_tty_sysfs(ch->ch_tun.un_sysfs); |
| tty_unregister_device(brd->serial_driver, i); |
| } |
| |
| if (ch->ch_pun.un_sysfs) { |
| dgap_remove_tty_sysfs(ch->ch_pun.un_sysfs); |
| tty_unregister_device(brd->print_driver, i); |
| } |
| i--; |
| } |
| |
| for (i = 0; i < brd->nasync; i++) { |
| tty_port_destroy(&brd->serial_ports[i]); |
| tty_port_destroy(&brd->printer_ports[i]); |
| } |
| |
| kfree(brd->printer_ports); |
| brd->printer_ports = NULL; |
| |
| free_serial_ports: |
| kfree(brd->serial_ports); |
| brd->serial_ports = NULL; |
| |
| return ret; |
| } |
| |
| /* |
| * dgap_cleanup_tty() |
| * |
| * Uninitialize the TTY portion of this driver. Free all memory and |
| * resources. |
| */ |
| static void dgap_cleanup_tty(struct board_t *brd) |
| { |
| struct device *dev; |
| unsigned int i; |
| |
| for (i = 0; i < brd->nasync; i++) { |
| tty_port_destroy(&brd->serial_ports[i]); |
| dev = brd->channels[i]->ch_tun.un_sysfs; |
| dgap_remove_tty_sysfs(dev); |
| tty_unregister_device(brd->serial_driver, i); |
| } |
| tty_unregister_driver(brd->serial_driver); |
| put_tty_driver(brd->serial_driver); |
| kfree(brd->serial_ports); |
| |
| for (i = 0; i < brd->nasync; i++) { |
| tty_port_destroy(&brd->printer_ports[i]); |
| dev = brd->channels[i]->ch_pun.un_sysfs; |
| dgap_remove_tty_sysfs(dev); |
| tty_unregister_device(brd->print_driver, i); |
| } |
| tty_unregister_driver(brd->print_driver); |
| put_tty_driver(brd->print_driver); |
| kfree(brd->printer_ports); |
| } |
| |
| static int dgap_request_irq(struct board_t *brd) |
| { |
| int rc; |
| |
| if (!brd || brd->magic != DGAP_BOARD_MAGIC) |
| return -ENODEV; |
| |
| /* |
| * Set up our interrupt handler if we are set to do interrupts. |
| */ |
| if (dgap_config_get_useintr(brd) && brd->irq) { |
| |
| rc = request_irq(brd->irq, dgap_intr, IRQF_SHARED, "DGAP", brd); |
| |
| if (!rc) |
| brd->intr_used = 1; |
| } |
| return 0; |
| } |
| |
| static void dgap_free_irq(struct board_t *brd) |
| { |
| if (brd->intr_used && brd->irq) |
| free_irq(brd->irq, brd); |
| } |
| |
| static int dgap_firmware_load(struct pci_dev *pdev, int card_type, |
| struct board_t *brd) |
| { |
| const struct firmware *fw; |
| char *tmp_ptr; |
| int ret; |
| char *dgap_config_buf; |
| |
| dgap_get_vpd(brd); |
| dgap_do_reset_board(brd); |
| |
| if (fw_info[card_type].conf_name) { |
| ret = request_firmware(&fw, fw_info[card_type].conf_name, |
| &pdev->dev); |
| if (ret) { |
| dev_err(&pdev->dev, "config file %s not found\n", |
| fw_info[card_type].conf_name); |
| return ret; |
| } |
| |
| dgap_config_buf = kzalloc(fw->size + 1, GFP_KERNEL); |
| if (!dgap_config_buf) { |
| release_firmware(fw); |
| return -ENOMEM; |
| } |
| |
| memcpy(dgap_config_buf, fw->data, fw->size); |
| release_firmware(fw); |
| |
| /* |
| * preserve dgap_config_buf |
| * as dgap_parsefile would |
| * otherwise alter it. |
| */ |
| tmp_ptr = dgap_config_buf; |
| |
| if (dgap_parsefile(&tmp_ptr) != 0) { |
| kfree(dgap_config_buf); |
| return -EINVAL; |
| } |
| kfree(dgap_config_buf); |
| } |
| |
| /* |
| * Match this board to a config the user created for us. |
| */ |
| brd->bd_config = |
| dgap_find_config(brd->type, brd->pci_bus, brd->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 (brd->type == PAPORT8 && !brd->bd_config) |
| brd->bd_config = |
| dgap_find_config(PAPORT4, brd->pci_bus, brd->pci_slot); |
| |
| if (!brd->bd_config) { |
| dev_err(&pdev->dev, "No valid configuration found\n"); |
| return -EINVAL; |
| } |
| |
| if (fw_info[card_type].bios_name) { |
| ret = request_firmware(&fw, fw_info[card_type].bios_name, |
| &pdev->dev); |
| if (ret) { |
| dev_err(&pdev->dev, "bios file %s not found\n", |
| fw_info[card_type].bios_name); |
| return ret; |
| } |
| dgap_do_bios_load(brd, fw->data, fw->size); |
| release_firmware(fw); |
| |
| /* Wait for BIOS to test board... */ |
| ret = dgap_test_bios(brd); |
| if (ret) |
| return ret; |
| } |
| |
| if (fw_info[card_type].fep_name) { |
| ret = request_firmware(&fw, fw_info[card_type].fep_name, |
| &pdev->dev); |
| if (ret) { |
| dev_err(&pdev->dev, "dgap: fep file %s not found\n", |
| fw_info[card_type].fep_name); |
| return ret; |
| } |
| dgap_do_fep_load(brd, fw->data, fw->size); |
| release_firmware(fw); |
| |
| /* Wait for FEP to load on board... */ |
| ret = dgap_test_fep(brd); |
| if (ret) |
| return ret; |
| } |
| |
| #ifdef DIGI_CONCENTRATORS_SUPPORTED |
| /* |
| * 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); |
| /* Nonzero if FEP is requesting concentrator image. */ |
| check = readw(chk_addr); |
| vaddr = brd->re_map_membase; |
| } |
| |
| if (fw_info[card_type].con_name && check && vaddr) { |
| ret = request_firmware(&fw, fw_info[card_type].con_name, |
| &pdev->dev); |
| if (ret) { |
| dev_err(&pdev->dev, "conc file %s not found\n", |
| fw_info[card_type].con_name); |
| return ret; |
| } |
| /* Put concentrator firmware loading code here */ |
| offset = readw((u16 *) (vaddr + DOWNREQ)); |
| memcpy_toio(offset, fw->data, fw->size); |
| |
| dgap_do_conc_load(brd, (char *)fw->data, fw->size) |
| release_firmware(fw); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| /* |
| * dgap_tty_init() |
| * |
| * Init the tty subsystem. Called once per board after board has been |
| * downloaded and init'ed. |
| */ |
| static int dgap_tty_init(struct board_t *brd) |
| { |
| int i; |
| int tlw; |
| uint true_count; |
| u8 __iomem *vaddr; |
| u8 modem; |
| struct channel_t *ch; |
| struct bs_t __iomem *bs; |
| struct cm_t __iomem *cm; |
| int ret; |
| |
| /* |
| * Initialize board structure elements. |
| */ |
| |
| vaddr = brd->re_map_membase; |
| true_count = readw((vaddr + NCHAN)); |
| |
| brd->nasync = dgap_config_get_num_prts(brd); |
| |
| if (!brd->nasync) |
| brd->nasync = brd->maxports; |
| |
| if (brd->nasync > brd->maxports) |
| brd->nasync = brd->maxports; |
| |
| if (true_count != brd->nasync) { |
| dev_warn(&brd->pdev->dev, |
| "%s configured for %d ports, has %d ports.\n", |
| brd->name, brd->nasync, true_count); |
| |
| if ((brd->type == PPCM) && |
| (true_count == 64 || true_count == 0)) { |
| dev_warn(&brd->pdev->dev, |
| "Please make SURE the EBI cable running from the card\n"); |
| dev_warn(&brd->pdev->dev, |
| "to each EM module is plugged into EBI IN!\n"); |
| } |
| |
| brd->nasync = true_count; |
| |
| /* If no ports, don't bother going any further */ |
| if (!brd->nasync) { |
| brd->state = BOARD_FAILED; |
| brd->dpastatus = BD_NOFEP; |
| return -EIO; |
| } |
| } |
| |
| /* |
| * Allocate channel memory that might not have been allocated |
| * when the driver was first loaded. |
| */ |
| for (i = 0; i < brd->nasync; i++) { |
| brd->channels[i] = |
| kzalloc(sizeof(struct channel_t), GFP_KERNEL); |
| if (!brd->channels[i]) { |
| ret = -ENOMEM; |
| goto free_chan; |
| } |
| } |
| |
| ch = brd->channels[0]; |
| vaddr = brd->re_map_membase; |
| |
| bs = (struct bs_t __iomem *) ((ulong) vaddr + CHANBUF); |
| cm = (struct cm_t __iomem *) ((ulong) vaddr + CMDBUF); |
| |
| brd->bd_bs = bs; |
| |
| /* Set up channel variables */ |
| for (i = 0; i < brd->nasync; i++, ch = brd->channels[i], bs++) { |
| |
| spin_lock_init(&ch->ch_lock); |
| |
| /* Store all our magic numbers */ |
| ch->magic = DGAP_CHANNEL_MAGIC; |
| ch->ch_tun.magic = DGAP_UNIT_MAGIC; |
| ch->ch_tun.un_type = DGAP_SERIAL; |
| ch->ch_tun.un_ch = ch; |
| ch->ch_tun.un_dev = i; |
| |
| ch->ch_pun.magic = DGAP_UNIT_MAGIC; |
| ch->ch_pun.un_type = DGAP_PRINT; |
| ch->ch_pun.un_ch = ch; |
| ch->ch_pun.un_dev = i; |
| |
| ch->ch_vaddr = vaddr; |
| ch->ch_bs = bs; |
| ch->ch_cm = cm; |
| ch->ch_bd = brd; |
| ch->ch_portnum = i; |
| ch->ch_digi = dgap_digi_init; |
| |
| /* |
| * Set up digi dsr and dcd bits based on altpin flag. |
| */ |
| if (dgap_config_get_altpin(brd)) { |
| ch->ch_dsr = DM_CD; |
| ch->ch_cd = DM_DSR; |
| ch->ch_digi.digi_flags |= DIGI_ALTPIN; |
| } else { |
| ch->ch_cd = DM_CD; |
| ch->ch_dsr = DM_DSR; |
| } |
| |
| ch->ch_taddr = vaddr + (ioread16(&(ch->ch_bs->tx_seg)) << 4); |
| ch->ch_raddr = vaddr + (ioread16(&(ch->ch_bs->rx_seg)) << 4); |
| ch->ch_tx_win = 0; |
| ch->ch_rx_win = 0; |
| ch->ch_tsize = readw(&(ch->ch_bs->tx_max)) + 1; |
| ch->ch_rsize = readw(&(ch->ch_bs->rx_max)) + 1; |
| ch->ch_tstart = 0; |
| ch->ch_rstart = 0; |
| |
| /* |
| * Set queue water marks, interrupt mask, |
| * and general tty parameters. |
| */ |
| tlw = ch->ch_tsize >= 2000 ? ((ch->ch_tsize * 5) / 8) : |
| ch->ch_tsize / 2; |
| ch->ch_tlw = tlw; |
| |
| dgap_cmdw(ch, STLOW, tlw, 0); |
| |
| dgap_cmdw(ch, SRLOW, ch->ch_rsize / 2, 0); |
| |
| dgap_cmdw(ch, SRHIGH, 7 * ch->ch_rsize / 8, 0); |
| |
| ch->ch_mistat = readb(&(ch->ch_bs->m_stat)); |
| |
| init_waitqueue_head(&ch->ch_flags_wait); |
| init_waitqueue_head(&ch->ch_tun.un_flags_wait); |
| init_waitqueue_head(&ch->ch_pun.un_flags_wait); |
| |
| /* Turn on all modem interrupts for now */ |
| modem = (DM_CD | DM_DSR | DM_CTS | DM_RI); |
| writeb(modem, &(ch->ch_bs->m_int)); |
| |
| /* |
| * Set edelay to 0 if interrupts are turned on, |
| * otherwise set edelay to the usual 100. |
| */ |
| if (brd->intr_used) |
| writew(0, &(ch->ch_bs->edelay)); |
| else |
| writew(100, &(ch->ch_bs->edelay)); |
| |
| writeb(1, &(ch->ch_bs->idata)); |
| } |
| |
| return 0; |
| |
| free_chan: |
| while (--i >= 0) { |
| kfree(brd->channels[i]); |
| brd->channels[i] = NULL; |
| } |
| return ret; |
| } |
| |
| /* |
| * dgap_tty_free() |
| * |
| * Free the channles which are allocated in dgap_tty_init(). |
| */ |
| static void dgap_tty_free(struct board_t *brd) |
| { |
| int i; |
| |
| for (i = 0; i < brd->nasync; i++) |
| kfree(brd->channels[i]); |
| } |
| |
| static int dgap_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) |
| { |
| int rc; |
| struct board_t *brd; |
| |
| if (dgap_numboards >= MAXBOARDS) |
| return -EPERM; |
| |
| rc = pci_enable_device(pdev); |
| if (rc) |
| return -EIO; |
| |
| brd = dgap_found_board(pdev, ent->driver_data, dgap_numboards); |
| if (IS_ERR(brd)) |
| return PTR_ERR(brd); |
| |
| rc = dgap_firmware_load(pdev, ent->driver_data, brd); |
| if (rc) |
| goto cleanup_brd; |
| |
| rc = dgap_alloc_flipbuf(brd); |
| if (rc) |
| goto cleanup_brd; |
| |
| rc = dgap_tty_register(brd); |
| if (rc) |
| goto free_flipbuf; |
| |
| rc = dgap_request_irq(brd); |
| if (rc) |
| goto unregister_tty; |
| |
| /* |
| * Do tty device initialization. |
| */ |
| rc = dgap_tty_init(brd); |
| if (rc < 0) |
| goto free_irq; |
| |
| rc = dgap_tty_register_ports(brd); |
| if (rc) |
| goto tty_free; |
| |
| brd->state = BOARD_READY; |
| brd->dpastatus = BD_RUNNING; |
| |
| dgap_board[dgap_numboards++] = brd; |
| |
| return 0; |
| |
| tty_free: |
| dgap_tty_free(brd); |
| free_irq: |
| dgap_free_irq(brd); |
| unregister_tty: |
| dgap_tty_unregister(brd); |
| free_flipbuf: |
| dgap_free_flipbuf(brd); |
| cleanup_brd: |
| dgap_cleanup_nodes(); |
| dgap_unmap(brd); |
| kfree(brd); |
| |
| return rc; |
| } |
| |
| /* |
| * dgap_cleanup_board() |
| * |
| * Free all the memory associated with a board |
| */ |
| static void dgap_cleanup_board(struct board_t *brd) |
| { |
| unsigned int i; |
| |
| if (!brd || brd->magic != DGAP_BOARD_MAGIC) |
| return; |
| |
| dgap_free_irq(brd); |
| |
| tasklet_kill(&brd->helper_tasklet); |
| |
| dgap_unmap(brd); |
| |
| /* Free all allocated channels structs */ |
| for (i = 0; i < MAXPORTS ; i++) |
| kfree(brd->channels[i]); |
| |
| kfree(brd->flipbuf); |
| kfree(brd->flipflagbuf); |
| |
| dgap_board[brd->boardnum] = NULL; |
| |
| kfree(brd); |
| } |
| |
| static void dgap_remove_one(struct pci_dev *dev) |
| { |
| unsigned int i; |
| ulong lock_flags; |
| struct pci_driver *drv = to_pci_driver(dev->dev.driver); |
| |
| spin_lock_irqsave(&dgap_poll_lock, lock_flags); |
| dgap_poll_stop = 1; |
| spin_unlock_irqrestore(&dgap_poll_lock, lock_flags); |
| |
| /* Turn off poller right away. */ |
| del_timer_sync(&dgap_poll_timer); |
| |
| dgap_remove_driver_sysfiles(drv); |
| |
| device_destroy(dgap_class, MKDEV(DIGI_DGAP_MAJOR, 0)); |
| class_destroy(dgap_class); |
| unregister_chrdev(DIGI_DGAP_MAJOR, "dgap"); |
| |
| for (i = 0; i < dgap_numboards; ++i) { |
| dgap_remove_ports_sysfiles(dgap_board[i]); |
| dgap_cleanup_tty(dgap_board[i]); |
| dgap_cleanup_board(dgap_board[i]); |
| } |
| |
| dgap_cleanup_nodes(); |
| } |
| |
| static struct pci_driver dgap_driver = { |
| .name = "dgap", |
| .probe = dgap_init_one, |
| .id_table = dgap_pci_tbl, |
| .remove = dgap_remove_one, |
| }; |
| |
| /* |
| * Start of driver. |
| */ |
| static int dgap_start(void) |
| { |
| int rc; |
| unsigned long flags; |
| struct device *device; |
| |
| dgap_numboards = 0; |
| |
| pr_info("For the tools package please visit http://www.digi.com\n"); |
| |
| /* |
| * Register our base character device into the kernel. |
| */ |
| |
| /* |
| * Register management/dpa devices |
| */ |
| rc = register_chrdev(DIGI_DGAP_MAJOR, "dgap", &dgap_board_fops); |
| if (rc < 0) |
| return rc; |
| |
| dgap_class = class_create(THIS_MODULE, "dgap_mgmt"); |
| if (IS_ERR(dgap_class)) { |
| rc = PTR_ERR(dgap_class); |
| goto failed_class; |
| } |
| |
| device = device_create(dgap_class, NULL, |
| MKDEV(DIGI_DGAP_MAJOR, 0), |
| NULL, "dgap_mgmt"); |
| if (IS_ERR(device)) { |
| rc = PTR_ERR(device); |
| goto failed_device; |
| } |
| |
| /* Start the poller */ |
| spin_lock_irqsave(&dgap_poll_lock, flags); |
| setup_timer(&dgap_poll_timer, dgap_poll_handler, 0); |
| dgap_poll_timer.data = 0; |
| dgap_poll_time = jiffies + dgap_jiffies_from_ms(dgap_poll_tick); |
| dgap_poll_timer.expires = dgap_poll_time; |
| spin_unlock_irqrestore(&dgap_poll_lock, flags); |
| |
| add_timer(&dgap_poll_timer); |
| |
| return rc; |
| |
| failed_device: |
| class_destroy(dgap_class); |
| failed_class: |
| unregister_chrdev(DIGI_DGAP_MAJOR, "dgap"); |
| return rc; |
| } |
| |
| static void dgap_stop(void) |
| { |
| unsigned long lock_flags; |
| |
| spin_lock_irqsave(&dgap_poll_lock, lock_flags); |
| dgap_poll_stop = 1; |
| spin_unlock_irqrestore(&dgap_poll_lock, lock_flags); |
| |
| del_timer_sync(&dgap_poll_timer); |
| |
| device_destroy(dgap_class, MKDEV(DIGI_DGAP_MAJOR, 0)); |
| class_destroy(dgap_class); |
| unregister_chrdev(DIGI_DGAP_MAJOR, "dgap"); |
| } |
| |
| /************************************************************************ |
| * |
| * Driver load/unload functions |
| * |
| ************************************************************************/ |
| |
| /* |
| * init_module() |
| * |
| * Module load. This is where it all starts. |
| */ |
| static int dgap_init_module(void) |
| { |
| int rc; |
| |
| pr_info("%s, Digi International Part Number %s\n", DG_NAME, DG_PART); |
| |
| rc = dgap_start(); |
| if (rc) |
| return rc; |
| |
| rc = pci_register_driver(&dgap_driver); |
| if (rc) |
| goto err_stop; |
| |
| rc = dgap_create_driver_sysfiles(&dgap_driver); |
| if (rc) |
| goto err_unregister; |
| |
| dgap_driver_state = DRIVER_READY; |
| |
| return 0; |
| |
| err_unregister: |
| pci_unregister_driver(&dgap_driver); |
| err_stop: |
| dgap_stop(); |
| |
| return rc; |
| } |
| |
| /* |
| * dgap_cleanup_module() |
| * |
| * Module unload. This is where it all ends. |
| */ |
| static void dgap_cleanup_module(void) |
| { |
| if (dgap_numboards) |
| pci_unregister_driver(&dgap_driver); |
| } |
| |
| module_init(dgap_init_module); |
| module_exit(dgap_cleanup_module); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Digi International, http://www.digi.com"); |
| MODULE_DESCRIPTION("Driver for the Digi International EPCA PCI based product line"); |
| MODULE_SUPPORTED_DEVICE("dgap"); |