| /* | |
| * 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"); |