/* * 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. If the first character is a ^, then * this will match the first occurrence not in that group. */ static char *dgap_sindex(char *string, char *group) { char *ptr; if (!string || !group) return NULL; if (*group == '^') { group++; for (; *string; string++) { for (ptr = group; *ptr; ptr++) { if (*ptr == *string) break; } if (*ptr == '\0') return string; } } else { 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 - Poiter 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; } static void dgap_remove_one(struct pci_dev *dev) { /* Do Nothing */ } 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"); } /* * 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); } /************************************************************************ * * 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) { unsigned int i; ulong lock_flags; 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(&dgap_driver); 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(); 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");