/*****************************************************************************/ /* * baycom.c -- baycom ser12 and par96 radio modem driver. * * Copyright (C) 1996 Thomas Sailer (sailer@ife.ee.ethz.ch) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Please note that the GPL allows you to use the driver, NOT the radio. * In order to use the radio, you need a license from the communications * authority of your country. * * * Supported modems * * ser12: This is a very simple 1200 baud AFSK modem. The modem consists only * of a modulator/demodulator chip, usually a TI TCM3105. The computer * is responsible for regenerating the receiver bit clock, as well as * for handling the HDLC protocol. The modem connects to a serial port, * hence the name. Since the serial port is not used as an async serial * port, the kernel driver for serial ports cannot be used, and this * driver only supports standard serial hardware (8250, 16450, 16550) * * par96: This is a modem for 9600 baud FSK compatible to the G3RUH standard. * The modem does all the filtering and regenerates the receiver clock. * Data is transferred from and to the PC via a shift register. * The shift register is filled with 16 bits and an interrupt is * signalled. The PC then empties the shift register in a burst. This * modem connects to the parallel port, hence the name. The modem * leaves the implementation of the HDLC protocol and the scrambler * polynomial to the PC. * * par97: This is a redesign of the par96 modem by Henning Rech, DF9IC. The * modem is protocol compatible to par96, but uses only three low * power ICs and can therefore be fed from the parallel port and * does not require an additional power supply. * * * Command line options (insmod command line) * * major major number the driver should use; default 60 * modem modem type of the first channel (minor 0); 1=ser12, * 2=par96/par97, any other value invalid * iobase base address of the port; common values are for ser12 0x3f8, * 0x2f8, 0x3e8, 0x2e8 and for par96/par97 0x378, 0x278, 0x3bc * irq interrupt line of the port; common values are for ser12 3,4 * and for par96/par97 7 * options 0=use hardware DCD, 1=use software DCD * * * History: * 0.1 03.05.96 Renamed from ser12 0.5 and added support for par96 * Various resource allocation cleanups * 0.2 12.05.96 Changed major to allocated 51. Integrated into kernel * source tree * 0.3 04.06.96 Major bug fixed (forgot to wake up after write) which * interestingly manifested only with kernel ax25 * (the slip line discipline) * introduced bottom half and tq_baycom * HDLC processing now done with interrupts on */ /*****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* --------------------------------------------------------------------- */ #define BAYCOM_TYPE_NORMAL 0 /* not used */ #define TTY_DRIVER_TYPE_BAYCOM 6 /* * ser12 options: * BAYCOM_OPTIONS_SOFTDCD: if undefined, you must use the transmitters * hardware carrier detect circuitry, the driver will report DCD as soon as * there are transitions on the input line. Advantage: lower interrupt load * on the system. Disadvantage: slower, since hardware carrier detect * circuitry is usually slow. */ #define BUFLEN_RX 8192 #define BUFLEN_TX 8192 #define NR_PORTS 4 #define KISS_VERBOSE #define BAYCOM_MAGIC 0x3105bac0 /* --------------------------------------------------------------------- */ /* * user settable parameters (from the command line) */ #ifndef MODULE static #endif /* MODULE */ int major = BAYCOM_MAJOR; /* --------------------------------------------------------------------- */ static struct tty_struct *baycom_table[NR_PORTS]; static struct termios *baycom_termios[NR_PORTS]; static struct termios *baycom_termios_locked[NR_PORTS]; static int baycom_refcount; static struct tty_driver baycom_driver; static struct { int modem, iobase, irq, options; } baycom_ports[NR_PORTS] = { { BAYCOM_MODEM_INVALID, 0, 0, 0, }, }; /* --------------------------------------------------------------------- */ #define RBR(iobase) (iobase+0) #define THR(iobase) (iobase+0) #define IER(iobase) (iobase+1) #define IIR(iobase) (iobase+2) #define FCR(iobase) (iobase+2) #define LCR(iobase) (iobase+3) #define MCR(iobase) (iobase+4) #define LSR(iobase) (iobase+5) #define MSR(iobase) (iobase+6) #define SCR(iobase) (iobase+7) #define DLL(iobase) (iobase+0) #define DLM(iobase) (iobase+1) #define SER12_EXTENT 8 #define LPT_DATA(iobase) (iobase+0) #define LPT_STATUS(iobase) (iobase+1) #define LPT_CONTROL(iobase) (iobase+2) #define LPT_IRQ_ENABLE 0x10 #define PAR96_BURSTBITS 16 #define PAR96_BURST 4 #define PAR96_PTT 2 #define PAR96_TXBIT 1 #define PAR96_ACK 0x40 #define PAR96_RXBIT 0x20 #define PAR96_DCD 0x10 #define PAR97_POWER 0xf8 #define PAR96_EXTENT 3 /* ---------------------------------------------------------------------- */ struct access_params { int tx_delay; int tx_tail; int slottime; int ppersist; int fulldup; }; struct hdlc_state_rx { int rx_state; /* 0 = sync hunt, != 0 receiving */ unsigned int bitstream; unsigned int bitbuf; int numbits; unsigned int shreg1, shreg2; int len; unsigned char *bp; unsigned char buffer[BAYCOM_MAXFLEN+2]; /* make room for CRC */ }; struct hdlc_state_tx { /* * 0 = send flags * 1 = send txtail (flags) * 2 = send packet */ int tx_state; int numflags; unsigned int bitstream; unsigned int current_byte; unsigned char ptt; unsigned int bitbuf; int numbits; unsigned int shreg1, shreg2; int len; unsigned char *bp; unsigned char buffer[BAYCOM_MAXFLEN+2]; /* make room for CRC */ }; struct modem_state_ser12 { unsigned char last_sample; unsigned char interm_sample; unsigned int bit_pll; unsigned int dcd_shreg; int dcd_sum0, dcd_sum1, dcd_sum2; unsigned int dcd_time; unsigned char last_rxbit; unsigned char tx_bit; }; struct modem_state_par96 { int dcd_count; unsigned int dcd_shreg; unsigned long descram; unsigned long scram; }; struct modem_state { unsigned char dcd; short arb_divider; unsigned char flags; struct modem_state_ser12 ser12; struct modem_state_par96 par96; }; struct packet_buffer { unsigned int rd; unsigned int wr; unsigned int buflen; unsigned char *buffer; }; struct packet_hdr { unsigned int next; unsigned int len; /* packet following */ }; #ifdef BAYCOM_DEBUG struct bit_buffer { unsigned int rd; unsigned int wr; unsigned int shreg; unsigned char buffer[64]; }; struct debug_vals { unsigned long last_jiffies; unsigned cur_intcnt; unsigned last_intcnt; int cur_pllcorr; int last_pllcorr; }; #endif /* BAYCOM_DEBUG */ struct kiss_decode { unsigned char dec_state; /* 0 = hunt FEND */ unsigned char escaped; unsigned char pkt_buf[BAYCOM_MAXFLEN+1]; unsigned int wr; }; /* ---------------------------------------------------------------------- */ struct baycom_state { int magic; unsigned char modem_type; unsigned int iobase; unsigned int irq; unsigned int options; int opened; struct tty_struct *tty; #ifdef BAYCOM_USE_BH struct tq_struct tq_receiver, tq_transmitter, tq_arbitrate; #endif /* BAYCOM_USE_BH */ struct packet_buffer rx_buf; struct packet_buffer tx_buf; struct access_params ch_params; struct hdlc_state_rx hdlc_rx; struct hdlc_state_tx hdlc_tx; int calibrate; struct modem_state modem; #ifdef BAYCOM_DEBUG struct bit_buffer bitbuf_channel; struct bit_buffer bitbuf_hdlc; struct debug_vals debug_vals; #endif /* BAYCOM_DEBUG */ struct kiss_decode kiss_decode; struct baycom_statistics stat; }; /* --------------------------------------------------------------------- */ struct baycom_state baycom_state[NR_PORTS]; #ifdef BAYCOM_USE_BH DECLARE_TASK_QUEUE(tq_baycom); #endif /* BAYCOM_USE_BH */ /* --------------------------------------------------------------------- */ /* * the CRC routines are stolen from WAMPES * by Dieter Deyke */ static const unsigned short crc_ccitt_table[] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; /*---------------------------------------------------------------------------*/ static inline void append_crc_ccitt(unsigned char *buffer, int len) { unsigned int crc = 0xffff; for (;len>0;len--) crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buffer++) & 0xff]; crc ^= 0xffff; *buffer++ = crc; *buffer++ = crc >> 8; } /*---------------------------------------------------------------------------*/ static inline int check_crc_ccitt(const unsigned char *buf,int cnt) { unsigned int crc = 0xffff; for (; cnt > 0; cnt--) crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff]; return (crc & 0xffff) == 0xf0b8; } /*---------------------------------------------------------------------------*/ #if 0 static int calc_crc_ccitt(const unsigned char *buf,int cnt) { unsigned int crc = 0xffff; for (; cnt > 0; cnt--) crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff]; crc ^= 0xffff; return (crc & 0xffff); } #endif /* ---------------------------------------------------------------------- */ static int store_packet(struct packet_buffer *buf, unsigned char *data, char from_user, unsigned int len) { unsigned int free; struct packet_hdr *hdr; unsigned int needed = sizeof(struct packet_hdr)+len; free = buf->rd-buf->wr; if(buf->rd <= buf->wr) { free = buf->buflen - buf->wr; if((free < needed) && (buf->rd >= needed)) { hdr = (struct packet_hdr *)(buf->buffer+buf->wr); hdr->next = 0; hdr->len = 0; buf->wr = 0; free = buf->rd; } } if(free < needed) return 0; /* buffer overrun */ hdr = (struct packet_hdr *)(buf->buffer+buf->wr); if (from_user) memcpy_fromfs(hdr+1,data,len); else memcpy(hdr+1,data,len); hdr->len = len; hdr->next = buf->wr+needed; if (hdr->next + sizeof(struct packet_hdr) >= buf->buflen) hdr->next = 0; buf->wr = hdr->next; return 1; } /* ---------------------------------------------------------------------- */ static void get_packet(struct packet_buffer *buf, unsigned char **data, unsigned int *len) { struct packet_hdr *hdr; *data = NULL; *len = 0; if (buf->rd == buf->wr) return; hdr = (struct packet_hdr *)(buf->buffer+buf->rd); while (!(hdr->len)) { buf->rd = hdr->next; if (buf->rd == buf->wr) return; hdr = (struct packet_hdr *)(buf->buffer+buf->rd); } *data = (unsigned char *)(hdr+1); *len = hdr->len; } /* ---------------------------------------------------------------------- */ static void ack_packet(struct packet_buffer *buf) { struct packet_hdr *hdr; if (buf->rd == buf->wr) return; hdr = (struct packet_hdr *)(buf->buffer+buf->rd); buf->rd = hdr->next; } /* ---------------------------------------------------------------------- */ static int store_kiss_packet(struct packet_buffer *buf, unsigned char *data, unsigned int len) { unsigned char *bp = data; int ln = len; /* * variables of buf */ unsigned int rd; unsigned int wr; unsigned int buflen; unsigned char *buffer; if (!len || !data || !buf) return 0; buflen = buf->buflen; rd = buf->rd; wr = buf->wr; buffer = buf->buffer; #define ADD_CHAR(c) {\ buffer[wr++] = c;\ if (wr >= buflen) wr = 0;\ if (wr == rd) return 0;\ } #define ADD_KISSCHAR(c) {\ if (((c) & 0xff) == KISS_FEND) {\ ADD_CHAR(KISS_FESC);\ ADD_CHAR(KISS_TFEND);\ } else if (((c) & 0xff) == KISS_FESC) {\ ADD_CHAR(KISS_FESC);\ ADD_CHAR(KISS_TFESC);\ } else {\ ADD_CHAR(c);\ }\ } ADD_CHAR(KISS_FEND); ADD_KISSCHAR(KISS_CMD_DATA); for(; ln > 0; ln--,bp++) { ADD_KISSCHAR(*bp); } ADD_CHAR(KISS_FEND); buf->wr = wr; #undef ADD_CHAR #undef ADD_KISSCHAR return 1; } /* ---------------------------------------------------------------------- */ #ifdef BAYCOM_DEBUG static inline void add_bitbuffer(struct bit_buffer * buf, unsigned int bit) { unsigned char new; if (!buf) return; new = buf->shreg & 1; buf->shreg >>= 1; if (bit) buf->shreg |= 0x80; if (new) { buf->buffer[buf->wr] = buf->shreg; buf->wr = (buf->wr+1) % sizeof(buf->buffer); buf->shreg = 0x80; } } static inline void add_bitbuffer_word(struct bit_buffer * buf, unsigned int bits) { buf->buffer[buf->wr] = bits & 0xff; buf->wr = (buf->wr+1) % sizeof(buf->buffer); buf->buffer[buf->wr] = (bits >> 8) & 0xff; buf->wr = (buf->wr+1) % sizeof(buf->buffer); } #endif /* BAYCOM_DEBUG */ /* ---------------------------------------------------------------------- */ static inline unsigned int tenms_to_2flags(struct baycom_state *bc, unsigned int tenms) { switch (bc->modem_type) { case BAYCOM_MODEM_SER12: return tenms * 3 / 4; case BAYCOM_MODEM_PAR96: return tenms * 6; default: return 0; } } /* ---------------------------------------------------------------------- */ /* * The HDLC routines */ static inline int hdlc_rx_add_bytes(struct baycom_state *bc, unsigned int bits, int num) { int added = 0; while (bc->hdlc_rx.rx_state && num >= 8) { if (bc->hdlc_rx.len >= sizeof(bc->hdlc_rx.buffer)) { bc->hdlc_rx.rx_state = 0; return 0; } *bc->hdlc_rx.bp++ = bits >> (32-num); bc->hdlc_rx.len++; num -= 8; added += 8; } return added; } static inline void hdlc_rx_flag(struct baycom_state *bc) { if (bc->hdlc_rx.len < 4) return; if (!check_crc_ccitt(bc->hdlc_rx.buffer, bc->hdlc_rx.len)) return; bc->stat.rx_packets++; if (!store_kiss_packet(&bc->rx_buf, bc->hdlc_rx.buffer, bc->hdlc_rx.len-2)) bc->stat.rx_bufferoverrun++; } static void hdlc_rx_word(struct baycom_state *bc, unsigned int word) { int i; unsigned int mask1, mask2, mask3, mask4, mask5, mask6; if (!bc) return; word &= 0xffff; #ifdef BAYCOM_DEBUG add_bitbuffer_word(&bc->bitbuf_hdlc, word); #endif /* BAYCOM_DEBUG */ bc->hdlc_rx.bitstream >>= 16; bc->hdlc_rx.bitstream |= word << 16; bc->hdlc_rx.bitbuf >>= 16; bc->hdlc_rx.bitbuf |= word << 16; bc->hdlc_rx.numbits += 16; for(i = 15, mask1 = 0x1fc00, mask2 = 0x1fe00, mask3 = 0x0fc00, mask4 = 0x1f800, mask5 = 0xf800, mask6 = 0xffff; i >= 0; i--, mask1 <<= 1, mask2 <<= 1, mask3 <<= 1, mask4 <<= 1, mask5 <<= 1, mask6 = (mask6 << 1) | 1) { if ((bc->hdlc_rx.bitstream & mask1) == mask1) bc->hdlc_rx.rx_state = 0; /* abort received */ else if ((bc->hdlc_rx.bitstream & mask2) == mask3) { /* flag received */ if (bc->hdlc_rx.rx_state) { hdlc_rx_add_bytes(bc, bc->hdlc_rx.bitbuf << (8 + i), bc->hdlc_rx.numbits - 8 - i); hdlc_rx_flag(bc); } bc->hdlc_rx.len = 0; bc->hdlc_rx.bp = bc->hdlc_rx.buffer; bc->hdlc_rx.rx_state = 1; bc->hdlc_rx.numbits = i; } else if ((bc->hdlc_rx.bitstream & mask4) == mask5) { /* stuffed bit */ bc->hdlc_rx.numbits--; bc->hdlc_rx.bitbuf = (bc->hdlc_rx.bitbuf & (~mask6)) | ((bc->hdlc_rx.bitbuf & mask6) << 1); } } bc->hdlc_rx.numbits -= hdlc_rx_add_bytes(bc, bc->hdlc_rx.bitbuf, bc->hdlc_rx.numbits); } /* ---------------------------------------------------------------------- */ static unsigned int hdlc_tx_word(struct baycom_state *bc) { unsigned int mask1, mask2, mask3; int i; if (!bc || !bc->hdlc_tx.ptt) return 0; for (;;) { if (bc->hdlc_tx.numbits >= 16) { unsigned int ret = bc->hdlc_tx.bitbuf & 0xffff; bc->hdlc_tx.bitbuf >>= 16; bc->hdlc_tx.numbits -= 16; return ret; } switch (bc->hdlc_tx.tx_state) { default: bc->hdlc_tx.ptt = 0; bc->hdlc_tx.tx_state = 0; return 0; case 0: case 1: if (bc->hdlc_tx.numflags) { bc->hdlc_tx.numflags--; bc->hdlc_tx.bitbuf |= 0x7e7e << bc->hdlc_tx.numbits; bc->hdlc_tx.numbits += 16; break; } if (bc->hdlc_tx.tx_state == 1) { bc->hdlc_tx.ptt = 0; return 0; } get_packet(&bc->tx_buf, &bc->hdlc_tx.bp, &bc->hdlc_tx.len); if (!bc->hdlc_tx.bp || !bc->hdlc_tx.len) { bc->hdlc_tx.tx_state = 1; bc->hdlc_tx.numflags = tenms_to_2flags (bc, bc->ch_params.tx_tail); break; } if (bc->hdlc_tx.len >= BAYCOM_MAXFLEN) { bc->hdlc_tx.tx_state = 0; bc->hdlc_tx.numflags = 1; ack_packet(&bc->tx_buf); break; } memcpy(bc->hdlc_tx.buffer, bc->hdlc_tx.bp, bc->hdlc_tx.len); ack_packet(&bc->tx_buf); bc->hdlc_tx.bp = bc->hdlc_tx.buffer; append_crc_ccitt(bc->hdlc_tx.buffer, bc->hdlc_tx.len); /* the appended CRC */ bc->hdlc_tx.len += 2; bc->hdlc_tx.tx_state = 2; bc->hdlc_tx.bitstream = 0; bc->stat.tx_packets++; break; case 2: if (!bc->hdlc_tx.len) { bc->hdlc_tx.tx_state = 0; bc->hdlc_tx.numflags = 1; break; } bc->hdlc_tx.len--; bc->hdlc_tx.bitbuf |= *bc->hdlc_tx.bp << bc->hdlc_tx.numbits; bc->hdlc_tx.bitstream >>= 8; bc->hdlc_tx.bitstream |= (*bc->hdlc_tx.bp++) << 16; mask1 = 0x1f000; mask2 = 0x10000; mask3 = 0xffffffff >> (31-bc->hdlc_tx.numbits); bc->hdlc_tx.numbits += 8; for(i = 0; i < 8; i++, mask1 <<= 1, mask2 <<= 1, mask3 = (mask3 << 1) | 1) { if ((bc->hdlc_tx.bitstream & mask1) != mask1) continue; bc->hdlc_tx.bitstream &= ~mask2; bc->hdlc_tx.bitbuf = (bc->hdlc_tx.bitbuf & mask3) | ((bc->hdlc_tx.bitbuf & (~mask3)) << 1); bc->hdlc_tx.numbits++; mask3 = (mask3 << 1) | 1; } break; } } } /* ---------------------------------------------------------------------- */ static unsigned short random_seed; static inline unsigned short random_num(void) { random_seed = 28629 * random_seed + 157; return random_seed; } /* ---------------------------------------------------------------------- */ static inline void tx_arbitrate(struct baycom_state *bc) { unsigned char *bp; unsigned int len; if (!bc || bc->hdlc_tx.ptt || bc->modem.dcd) return; get_packet(&bc->tx_buf, &bp, &len); if (!bp || !len) return; if (!bc->ch_params.fulldup) { if ((random_num() % 256) > bc->ch_params.ppersist) return; } bc->hdlc_tx.tx_state = 0; bc->hdlc_tx.numflags = tenms_to_2flags(bc, bc->ch_params.tx_delay); bc->hdlc_tx.numbits = bc->hdlc_tx.bitbuf = bc->hdlc_tx.bitstream = 0; bc->hdlc_tx.ptt = 1; bc->stat.ptt_keyed++; } /* --------------------------------------------------------------------- */ #ifdef BAYCOM_DEBUG static void inline baycom_int_freq(struct baycom_state *bc) { unsigned long cur_jiffies = jiffies; /* * measure the interrupt frequency */ bc->debug_vals.cur_intcnt++; if ((cur_jiffies - bc->debug_vals.last_jiffies) >= HZ) { bc->debug_vals.last_jiffies = cur_jiffies; bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt; bc->debug_vals.cur_intcnt = 0; bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr; bc->debug_vals.cur_pllcorr = 0; } } #endif /* BAYCOM_DEBUG */ /* --------------------------------------------------------------------- */ static inline void rx_chars_to_flip(struct baycom_state *bc) { int flip_free; unsigned int cnt; unsigned int new_rd; unsigned long flags; if ((!bc) || (!bc->tty) || (bc->tty->flip.count >= TTY_FLIPBUF_SIZE) || (bc->rx_buf.rd == bc->rx_buf.wr) || (!bc->tty->flip.char_buf_ptr) || (!bc->tty->flip.flag_buf_ptr)) return; for(;;) { flip_free = TTY_FLIPBUF_SIZE - bc->tty->flip.count; if (bc->rx_buf.rd <= bc->rx_buf.wr) cnt = bc->rx_buf.wr - bc->rx_buf.rd; else cnt = bc->rx_buf.buflen - bc->rx_buf.rd; if ((flip_free <= 0) || (!cnt)) { tty_schedule_flip(bc->tty); return; } if (cnt > flip_free) cnt = flip_free; save_flags(flags); cli(); memcpy(bc->tty->flip.char_buf_ptr, bc->rx_buf.buffer+bc->rx_buf.rd, cnt); memset(bc->tty->flip.flag_buf_ptr, TTY_NORMAL, cnt); bc->tty->flip.count += cnt; bc->tty->flip.char_buf_ptr += cnt; bc->tty->flip.flag_buf_ptr += cnt; restore_flags(flags); new_rd = bc->rx_buf.rd+cnt; if (new_rd >= bc->rx_buf.buflen) new_rd -= bc->rx_buf.buflen; bc->rx_buf.rd = new_rd; } } /* --------------------------------------------------------------------- */ /* * ===================== SER12 specific routines ========================= */ static void inline ser12_set_divisor(struct baycom_state *bc, unsigned char divisor) { outb(0x81, LCR(bc->iobase)); /* DLAB = 1 */ outb(divisor, DLL(bc->iobase)); outb(0, DLM(bc->iobase)); outb(0x01, LCR(bc->iobase)); /* word length = 6 */ } /* --------------------------------------------------------------------- */ /* * must call the TX arbitrator every 10ms */ #define SER12_ARB_DIVIDER(bc) ((bc->options & BAYCOM_OPTIONS_SOFTDCD) ? \ 36 : 24) #define SER12_DCD_INTERVAL(bc) ((bc->options & BAYCOM_OPTIONS_SOFTDCD) ? \ 240 : 12) static void baycom_ser12_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct baycom_state *bc = (struct baycom_state *)dev_id; unsigned char cur_s; if (!bc || bc->magic != BAYCOM_MAGIC) return; /* * make sure the next interrupt is generated; * 0 must be used to power the modem; the modem draws its * power from the TxD line */ outb(0x00, THR(bc->iobase)); rx_chars_to_flip(bc); #ifdef BAYCOM_DEBUG baycom_int_freq(bc); #endif /* BAYCOM_DEBUG */ /* * check if transmitter active */ if (bc->hdlc_tx.ptt || bc->calibrate > 0) { ser12_set_divisor(bc, 12); /* one interrupt per channel bit */ /* * first output the last bit (!) then call HDLC transmitter, * since this may take quite long */ outb(0x0e | (bc->modem.ser12.tx_bit ? 1 : 0), MCR(bc->iobase)); if (bc->hdlc_tx.shreg1 <= 1) { if (bc->calibrate > 0) { bc->hdlc_tx.shreg1 = 0x10000; bc->calibrate--; } else { #ifdef BAYCOM_USE_BH bc->hdlc_tx.shreg1 = bc->hdlc_tx.shreg2; bc->hdlc_tx.shreg2 = 0; queue_task_irq_off(&bc->tq_transmitter, &tq_baycom); mark_bh(BAYCOM_BH); #ifdef HDLC_LOOPBACK bc->hdlc_rx.shreg2 = bc->hdlc_tx.shreg1; queue_task_irq_off(&bc->tq_receiver, &tq_baycom); #endif /* HDLC_LOOPBACK */ #else /* BAYCOM_USE_BH */ bc->hdlc_tx.shreg1 = hdlc_tx_word(bc) | 0x10000; #ifdef HDLC_LOOPBACK hdlc_rx_word(bc, bc->hdlc_tx.shreg1); #endif /* HDLC_LOOPBACK */ #endif /* BAYCOM_USE_BH */ } } if (!(bc->hdlc_tx.shreg1 & 1)) bc->modem.ser12.tx_bit = !bc->modem.ser12.tx_bit; bc->hdlc_tx.shreg1 >>= 1; return; } /* * do demodulator */ outb(0x0d, MCR(bc->iobase)); /* transmitter off */ cur_s = inb(MSR(bc->iobase)) & 0x10; /* the CTS line */ #ifdef BAYCOM_DEBUG add_bitbuffer(&bc->bitbuf_channel, cur_s); #endif /* BAYCOM_DEBUG */ bc->modem.ser12.dcd_shreg <<= 1; if(cur_s != bc->modem.ser12.last_sample) { bc->modem.ser12.dcd_shreg |= 1; if (bc->options & BAYCOM_OPTIONS_SOFTDCD) { unsigned int dcdspos, dcdsneg; dcdspos = dcdsneg = 0; dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1); if (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe)) dcdspos += 2; dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1); dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1); dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1); bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg; } else bc->modem.ser12.dcd_sum0--; } bc->modem.ser12.last_sample = cur_s; if(!bc->modem.ser12.dcd_time) { bc->modem.dcd = (bc->modem.ser12.dcd_sum0 + bc->modem.ser12.dcd_sum1 + bc->modem.ser12.dcd_sum2) < 0; bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1; bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0; /* offset to ensure DCD off on silent input */ bc->modem.ser12.dcd_sum0 = 2; bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc); } bc->modem.ser12.dcd_time--; if (bc->options & BAYCOM_OPTIONS_SOFTDCD) { /* * PLL code for the improved software DCD algorithm */ if (bc->modem.ser12.interm_sample) { /* * intermediate sample; set timing correction to normal */ ser12_set_divisor(bc, 4); } else { /* * do PLL correction and call HDLC receiver */ switch (bc->modem.ser12.dcd_shreg & 7) { case 1: /* transition too late */ ser12_set_divisor(bc, 5); #ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr++; #endif /* BAYCOM_DEBUG */ break; case 4: /* transition too early */ ser12_set_divisor(bc, 3); #ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr--; #endif /* BAYCOM_DEBUG */ break; default: ser12_set_divisor(bc, 4); break; } bc->hdlc_rx.shreg1 >>= 1; if (bc->modem.ser12.last_sample == bc->modem.ser12.last_rxbit) bc->hdlc_rx.shreg1 |= 0x10000; bc->modem.ser12.last_rxbit = bc->modem.ser12.last_sample; } if (++bc->modem.ser12.interm_sample >= 3) bc->modem.ser12.interm_sample = 0; } else { /* * PLL algorithm for the hardware squelch DCD algorithm */ if (bc->modem.ser12.interm_sample) { /* * intermediate sample; set timing correction to normal */ ser12_set_divisor(bc, 6); } else { /* * do PLL correction and call HDLC receiver */ switch (bc->modem.ser12.dcd_shreg & 3) { case 1: /* transition too late */ ser12_set_divisor(bc, 7); #ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr++; #endif /* BAYCOM_DEBUG */ break; case 2: /* transition too early */ ser12_set_divisor(bc, 5); #ifdef BAYCOM_DEBUG bc->debug_vals.cur_pllcorr--; #endif /* BAYCOM_DEBUG */ break; default: ser12_set_divisor(bc, 6); break; } bc->hdlc_rx.shreg1 >>= 1; if (bc->modem.ser12.last_sample == bc->modem.ser12.last_rxbit) bc->hdlc_rx.shreg1 |= 0x10000; bc->modem.ser12.last_rxbit = bc->modem.ser12.last_sample; } bc->modem.ser12.interm_sample = !bc->modem.ser12.interm_sample; } if (bc->hdlc_rx.shreg1 & 1) { #ifdef BAYCOM_USE_BH bc->hdlc_rx.shreg2 = (bc->hdlc_rx.shreg1 >> 1) | 0x10000; queue_task_irq_off(&bc->tq_receiver, &tq_baycom); mark_bh(BAYCOM_BH); #else /* BAYCOM_USE_BH */ hdlc_rx_word(bc, bc->hdlc_rx.shreg1 >> 1); #endif /* BAYCOM_USE_BH */ bc->hdlc_rx.shreg1 = 0x10000; } if (--bc->modem.arb_divider <= 0) { #ifdef BAYCOM_USE_BH queue_task_irq_off(&bc->tq_arbitrate, &tq_baycom); mark_bh(BAYCOM_BH); #else /* BAYCOM_USE_BH */ tx_arbitrate(bc); #endif /* BAYCOM_USE_BH */ bc->modem.arb_divider = bc->ch_params.slottime * SER12_ARB_DIVIDER(bc); } } /* --------------------------------------------------------------------- */ enum uart { c_uart_unknown, c_uart_8250, c_uart_16450, c_uart_16550, c_uart_16550A}; static const char *uart_str[] = { "unknown", "8250", "16450", "16550", "16550A" }; static enum uart ser12_check_uart(unsigned int iobase) { unsigned char b1,b2,b3; enum uart u; enum uart uart_tab[] = { c_uart_16450, c_uart_unknown, c_uart_16550, c_uart_16550A }; b1 = inb(MCR(iobase)); outb(b1 | 0x10, MCR(iobase)); /* loopback mode */ b2 = inb(MSR(iobase)); outb(0x1a, MCR(iobase)); b3 = inb(MSR(iobase)) & 0xf0; outb(b1, MCR(iobase)); /* restore old values */ outb(b2, MSR(iobase)); if (b3 != 0x90) return c_uart_unknown; inb(RBR(iobase)); inb(RBR(iobase)); outb(0x01, FCR(iobase)); /* enable FIFOs */ u = uart_tab[(inb(IIR(iobase)) >> 6) & 3]; if (u == c_uart_16450) { outb(0x5a, SCR(iobase)); b1 = inb(SCR(iobase)); outb(0xa5, SCR(iobase)); b2 = inb(SCR(iobase)); if ((b1 != 0x5a) || (b2 != 0xa5)) u = c_uart_8250; } return u; } /* --------------------------------------------------------------------- */ static int ser12_allocate_resources(unsigned int iobase, unsigned int irq, unsigned int options) { enum uart u; if (!iobase || iobase > 0xfff || irq < 2 || irq > 15) return -ENXIO; if (check_region(iobase, SER12_EXTENT)) return -EACCES; if ((u = ser12_check_uart(iobase)) == c_uart_unknown) return -EIO; request_region(iobase, SER12_EXTENT, "baycom_ser12"); outb(0, FCR(iobase)); /* disable FIFOs */ outb(0x0d, MCR(iobase)); printk(KERN_INFO "baycom: ser12 at iobase 0x%x irq %u options 0x%x " "uart %s\n", iobase, irq, options, uart_str[u]); return 0; } /* --------------------------------------------------------------------- */ static void ser12_deallocate_resources(struct baycom_state *bc) { if (!bc || bc->modem_type != BAYCOM_MODEM_SER12) return; /* * disable interrupts */ outb(0, IER(bc->iobase)); outb(1, MCR(bc->iobase)); /* * this should prevent kernel: Trying to free IRQx * messages */ if (bc->opened > 0) free_irq(bc->irq, bc); release_region(bc->iobase, SER12_EXTENT); bc->modem_type = BAYCOM_MODEM_INVALID; printk(KERN_INFO "baycom: release ser12 at iobase 0x%x irq %u\n", bc->iobase, bc->irq); bc->iobase = bc->irq = bc->options = 0; } /* --------------------------------------------------------------------- */ static int ser12_on_open(struct baycom_state *bc) { if (!bc || bc->modem_type != BAYCOM_MODEM_SER12) return -ENXIO; /* * set the SIO to 6 Bits/character and 19200 or 28800 baud, so that * we get exactly (hopefully) 2 or 3 interrupts per radio symbol, * depending on the usage of the software DCD routine */ ser12_set_divisor(bc, (bc->options & BAYCOM_OPTIONS_SOFTDCD) ? 4 : 6); outb(0x0d, MCR(bc->iobase)); outb(0, IER(bc->iobase)); if (request_irq(bc->irq, baycom_ser12_interrupt, SA_INTERRUPT, "baycom_ser12", bc)) return -EBUSY; /* * enable transmitter empty interrupt */ outb(2, IER(bc->iobase)); /* * the value here serves to power the modem */ outb(0x00, THR(bc->iobase)); return 0; } /* --------------------------------------------------------------------- */ static void ser12_on_close(struct baycom_state *bc) { if (!bc || bc->modem_type != BAYCOM_MODEM_SER12) return; /* * disable interrupts */ outb(0, IER(bc->iobase)); outb(1, MCR(bc->iobase)); free_irq(bc->irq, bc); } /* --------------------------------------------------------------------- */ /* * ===================== PAR96 specific routines ========================= */ #define PAR96_DESCRAM_TAP1 0x20000 #define PAR96_DESCRAM_TAP2 0x01000 #define PAR96_DESCRAM_TAP3 0x00001 #define PAR96_DESCRAM_TAPSH1 17 #define PAR96_DESCRAM_TAPSH2 12 #define PAR96_DESCRAM_TAPSH3 0 #define PAR96_SCRAM_TAP1 0x20000 /* X^17 */ #define PAR96_SCRAM_TAPN 0x00021 /* X^0+X^5 */ /* --------------------------------------------------------------------- */ static void baycom_par96_interrupt(int irq, void *dev_id, struct pt_regs *regs) { register struct baycom_state *bc = (struct baycom_state *)dev_id; int i; unsigned int data, descx, mask, mask2; if (!bc || bc->magic != BAYCOM_MAGIC) return; rx_chars_to_flip(bc); #ifdef BAYCOM_DEBUG baycom_int_freq(bc); #endif /* BAYCOM_DEBUG */ /* * check if transmitter active */ if (bc->hdlc_tx.ptt || bc->calibrate > 0) { /* * first output the last 16 bits (!) then call HDLC * transmitter, since this may take quite long * do the differential encoder and the scrambler on the fly */ data = bc->hdlc_tx.shreg1; for(i = 0; i < PAR96_BURSTBITS; i++, data >>= 1) { unsigned char val = PAR97_POWER; bc->modem.par96.scram = ((bc->modem.par96.scram << 1) | (bc->modem.par96.scram & 1)); if (!(data & 1)) bc->modem.par96.scram ^= 1; if (bc->modem.par96.scram & (PAR96_SCRAM_TAP1 << 1)) bc->modem.par96.scram ^= (PAR96_SCRAM_TAPN << 1); if (bc->modem.par96.scram & (PAR96_SCRAM_TAP1 << 2)) val |= PAR96_TXBIT; outb(val, LPT_DATA(bc->iobase)); outb(val | PAR96_BURST, LPT_DATA(bc->iobase)); } if (bc->calibrate > 0) { bc->hdlc_tx.shreg1 = 0x10000; bc->calibrate--; } else { #ifdef BAYCOM_USE_BH bc->hdlc_tx.shreg1 = bc->hdlc_tx.shreg2; bc->hdlc_tx.shreg2 = 0; queue_task_irq_off(&bc->tq_transmitter, &tq_baycom); mark_bh(BAYCOM_BH); #ifdef HDLC_LOOPBACK bc->hdlc_rx.shreg2 = bc->hdlc_tx.shreg1; queue_task_irq_off(&bc->tq_receiver, &tq_baycom); #endif /* HDLC_LOOPBACK */ #else /* BAYCOM_USE_BH */ bc->hdlc_tx.shreg1 = hdlc_tx_word(bc); #ifdef HDLC_LOOPBACK hdlc_rx_word(bc, bc->hdlc_tx.shreg1); #endif /* HDLC_LOOPBACK */ #endif /* BAYCOM_USE_BH */ } return; } /* * do receiver; differential decode and descramble on the fly */ for(data = i = 0; i < PAR96_BURSTBITS; i++) { bc->modem.par96.descram = (bc->modem.par96.descram << 1); if (inb(LPT_STATUS(bc->iobase)) & PAR96_RXBIT) bc->modem.par96.descram |= 1; descx = bc->modem.par96.descram ^ (bc->modem.par96.descram >> 1); /* now the diff decoded data is inverted in descram */ outb(PAR97_POWER | PAR96_PTT, LPT_DATA(bc->iobase)); descx ^= ((descx >> PAR96_DESCRAM_TAPSH1) ^ (descx >> PAR96_DESCRAM_TAPSH2)); data >>= 1; if (!(descx & 1)) data |= 0x8000; outb(PAR97_POWER | PAR96_PTT | PAR96_BURST, LPT_DATA(bc->iobase)); } #ifdef BAYCOM_USE_BH bc->hdlc_rx.shreg2 = bc->hdlc_rx.shreg1; bc->hdlc_rx.shreg1 = data | 0x10000; queue_task_irq_off(&bc->tq_receiver, &tq_baycom); mark_bh(BAYCOM_BH); #else /* BAYCOM_USE_BH */ hdlc_rx_word(bc, data); #endif /* BAYCOM_USE_BH */ /* * do DCD algorithm */ if (bc->options & BAYCOM_OPTIONS_SOFTDCD) { bc->modem.par96.dcd_shreg = (bc->modem.par96.dcd_shreg >> 16) | (data << 16); /* search for flags and set the dcd counter appropriately */ for(mask = 0x1fe00, mask2 = 0xfc00, i = 0; i < PAR96_BURSTBITS; i++, mask <<= 1, mask2 <<= 1) if ((bc->modem.par96.dcd_shreg & mask) == mask2) bc->modem.par96.dcd_count = BAYCOM_MAXFLEN+4; /* check for abort/noise sequences */ for(mask = 0x1fe00, mask2 = 0x1fe00, i = 0; i < PAR96_BURSTBITS; i++, mask <<= 1, mask2 <<= 1) if (((bc->modem.par96.dcd_shreg & mask) == mask2) && (bc->modem.par96.dcd_count >= 0)) bc->modem.par96.dcd_count -= BAYCOM_MAXFLEN-10; /* decrement and set the dcd variable */ if (bc->modem.par96.dcd_count >= 0) bc->modem.par96.dcd_count -= 2; bc->modem.dcd = bc->modem.par96.dcd_count > 0; } else { bc->modem.dcd = !!(inb(LPT_STATUS(bc->iobase)) & PAR96_DCD); } if (--bc->modem.arb_divider <= 0) { #ifdef BAYCOM_USE_BH queue_task_irq_off(&bc->tq_arbitrate, &tq_baycom); mark_bh(BAYCOM_BH); #else /* BAYCOM_USE_BH */ tx_arbitrate(bc); #endif /* BAYCOM_USE_BH */ bc->modem.arb_divider = bc->ch_params.slottime * 6; } } /* --------------------------------------------------------------------- */ static int par96_check_lpt(unsigned int iobase) { unsigned char b1,b2; int i; b1 = inb(LPT_DATA(iobase)); b2 = inb(LPT_CONTROL(iobase)); outb(0xaa, LPT_DATA(iobase)); i = inb(LPT_DATA(iobase)) == 0xaa; outb(0x55, LPT_DATA(iobase)); i &= inb(LPT_DATA(iobase)) == 0x55; outb(0x0a, LPT_CONTROL(iobase)); i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x0a; outb(0x05, LPT_CONTROL(iobase)); i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x05; outb(b1, LPT_DATA(iobase)); outb(b2, LPT_CONTROL(iobase)); return !i; } /* --------------------------------------------------------------------- */ static int par96_allocate_resources(unsigned int iobase, unsigned int irq, unsigned int options) { if (!iobase || iobase > 0xfff || irq < 2 || irq > 15) return -ENXIO; if (check_region(iobase, PAR96_EXTENT)) return -EACCES; if (par96_check_lpt(iobase)) return -EIO; request_region(iobase, PAR96_EXTENT, "baycom_par96"); outb(0, LPT_CONTROL(iobase)); /* disable interrupt */ outb(PAR96_PTT | PAR97_POWER, LPT_DATA(iobase)); /* switch off PTT */ printk(KERN_INFO "baycom: par96 at iobase 0x%x irq %u options 0x%x\n", iobase, irq, options); return 0; } /* --------------------------------------------------------------------- */ static void par96_deallocate_resources(struct baycom_state *bc) { if (!bc || bc->modem_type != BAYCOM_MODEM_PAR96) return; outb(0, LPT_CONTROL(bc->iobase)); /* disable interrupt */ outb(PAR96_PTT, LPT_DATA(bc->iobase)); /* switch off PTT */ /* * this should prevent kernel: Trying to free IRQx * messages */ if (bc->opened > 0) free_irq(bc->irq, bc); release_region(bc->iobase, PAR96_EXTENT); bc->modem_type = BAYCOM_MODEM_INVALID; printk(KERN_INFO "baycom: release par96 at iobase 0x%x irq %u\n", bc->iobase, bc->irq); bc->iobase = bc->irq = bc->options = 0; } /* --------------------------------------------------------------------- */ static int par96_on_open(struct baycom_state *bc) { if (!bc || bc->modem_type != BAYCOM_MODEM_PAR96) return -ENXIO; outb(0, LPT_CONTROL(bc->iobase)); /* disable interrupt */ /* switch off PTT */ outb(PAR96_PTT | PAR97_POWER, LPT_DATA(bc->iobase)); if (request_irq(bc->irq, baycom_par96_interrupt, SA_INTERRUPT, "baycom_par96", bc)) return -EBUSY; outb(LPT_IRQ_ENABLE, LPT_CONTROL(bc->iobase)); /* enable interrupt */ return 0; } /* --------------------------------------------------------------------- */ static void par96_on_close(struct baycom_state *bc) { if (!bc || bc->modem_type != BAYCOM_MODEM_PAR96) return; outb(0, LPT_CONTROL(bc->iobase)); /* disable interrupt */ /* switch off PTT */ outb(PAR96_PTT | PAR97_POWER, LPT_DATA(bc->iobase)); free_irq(bc->irq, bc); } /* --------------------------------------------------------------------- */ /* * ===================== Bottom half (soft interrupt) ==================== */ #ifdef BAYCOM_USE_BH static void bh_receiver(void *private) { struct baycom_state *bc = (struct baycom_state *)private; unsigned int temp; if (!bc || bc->magic != BAYCOM_MAGIC) return; if (!bc->hdlc_rx.shreg2) return; temp = bc->hdlc_rx.shreg2; bc->hdlc_rx.shreg2 = 0; hdlc_rx_word(bc, temp); } /* --------------------------------------------------------------------- */ static void bh_transmitter(void *private) { struct baycom_state *bc = (struct baycom_state *)private; if (!bc || bc->magic != BAYCOM_MAGIC) return; if (bc->hdlc_tx.shreg2) return; bc->hdlc_tx.shreg2 = hdlc_tx_word(bc) | 0x10000; } /* --------------------------------------------------------------------- */ static void bh_arbitrate(void *private) { struct baycom_state *bc = (struct baycom_state *)private; if (!bc || bc->magic != BAYCOM_MAGIC) return; tx_arbitrate(bc); } /* --------------------------------------------------------------------- */ static void baycom_bottom_half(void) { run_task_queue(&tq_baycom); } #endif /* BAYCOM_USE_BH */ /* --------------------------------------------------------------------- */ /* * ===================== TTY interface routines ========================== */ static inline int baycom_paranoia_check(struct baycom_state *bc, const char *routine) { if (!bc || bc->magic != BAYCOM_MAGIC) { printk(KERN_ERR "baycom: bad magic number for baycom struct " "in routine %s\n", routine); return 1; } return 0; } /* --------------------------------------------------------------------- */ /* * Here the tty driver code starts */ static void baycom_put_fend(struct baycom_state *bc) { if (bc->kiss_decode.wr <= 0 || (bc->kiss_decode.pkt_buf[0] & 0xf0) != 0) return; switch (bc->kiss_decode.pkt_buf[0] & 0xf) { case KISS_CMD_DATA: if (bc->kiss_decode.wr <= 8) break; if (!store_packet(&bc->tx_buf, bc->kiss_decode.pkt_buf+1, 0, bc->kiss_decode.wr-1)) bc->stat.tx_bufferoverrun++; break; case KISS_CMD_TXDELAY: if (bc->kiss_decode.wr < 2) break; bc->ch_params.tx_delay = bc->kiss_decode.pkt_buf[1]; #ifdef KISS_VERBOSE printk(KERN_INFO "baycom: TX delay = %ums\n", bc->ch_params.tx_delay * 10); #endif /* KISS_VERBOSE */ break; case KISS_CMD_PPERSIST: if (bc->kiss_decode.wr < 2) break; bc->ch_params.ppersist = bc->kiss_decode.pkt_buf[1]; #ifdef KISS_VERBOSE printk(KERN_INFO "baycom: p-persistence = %u\n", bc->ch_params.ppersist); #endif /* KISS_VERBOSE */ break; case KISS_CMD_SLOTTIME: if (bc->kiss_decode.wr < 2) break; bc->ch_params.slottime = bc->kiss_decode.pkt_buf[1]; #ifdef KISS_VERBOSE printk(KERN_INFO "baycom: slottime = %ums\n", bc->ch_params.slottime * 10); #endif /* KISS_VERBOSE */ break; case KISS_CMD_TXTAIL: if (bc->kiss_decode.wr < 2) break; bc->ch_params.tx_tail = bc->kiss_decode.pkt_buf[1]; #ifdef KISS_VERBOSE printk(KERN_INFO "baycom: TX tail = %ums\n", bc->ch_params.tx_tail * 10); #endif /* KISS_VERBOSE */ break; case KISS_CMD_FULLDUP: if (bc->kiss_decode.wr < 2) break; bc->ch_params.fulldup = bc->kiss_decode.pkt_buf[1]; #ifdef KISS_VERBOSE printk(KERN_INFO "baycom: %s duplex\n", bc->ch_params.fulldup ? "full" : "half"); #endif /* KISS_VERBOSE */ break; default: #ifdef KISS_VERBOSE printk(KERN_INFO "baycom: unhandled KISS packet code %u\n", bc->kiss_decode.pkt_buf[0] & 0xf); #endif /* KISS_VERBOSE */ break; } } /* --------------------------------------------------------------------- */ static void baycom_put_char(struct tty_struct *tty, unsigned char ch) { struct baycom_state *bc; if (!tty) return; if (baycom_paranoia_check(bc = tty->driver_data, "put_char")) return; if (ch == KISS_FEND) { baycom_put_fend(bc); bc->kiss_decode.wr = 0; bc->kiss_decode.escaped = 0; bc->kiss_decode.dec_state = 1; return; } if (!bc->kiss_decode.dec_state) return; if (ch == KISS_FESC) { bc->kiss_decode.escaped = 1; return; } if (bc->kiss_decode.wr >= sizeof(bc->kiss_decode.pkt_buf)) { bc->kiss_decode.wr = 0; bc->kiss_decode.dec_state = 0; return; } if (bc->kiss_decode.escaped) { if (ch == KISS_TFEND) bc->kiss_decode.pkt_buf[bc->kiss_decode.wr++] = KISS_FEND; else if (ch == KISS_TFESC) bc->kiss_decode.pkt_buf[bc->kiss_decode.wr++] = KISS_FESC; else { bc->kiss_decode.wr = 0; bc->kiss_decode.dec_state = 0; } bc->kiss_decode.escaped = 0; return; } bc->kiss_decode.pkt_buf[bc->kiss_decode.wr++] = ch; } /* --------------------------------------------------------------------- */ static int baycom_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count) { int c; const unsigned char *bp; struct baycom_state *bc; if (!tty || !buf || count <= 0) return count; if (baycom_paranoia_check(bc = tty->driver_data, "write")) return count; if (from_user) { for(c = count, bp = buf; c > 0; c--,bp++) baycom_put_char(tty, get_user(bp)); } else { for(c = count, bp = buf; c > 0; c--,bp++) baycom_put_char(tty, *bp); } if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); return count; } /* --------------------------------------------------------------------- */ static int baycom_write_room(struct tty_struct *tty) { int free; struct baycom_state *bc; if (!tty) return 0; if (baycom_paranoia_check(bc = tty->driver_data, "write_room")) return 0; free = bc->tx_buf.rd - bc->tx_buf.wr; if (free <= 0) { free = bc->tx_buf.buflen - bc->tx_buf.wr; if (free < bc->tx_buf.rd) free = bc->tx_buf.rd; /* we may fold */ } return free / 2; /* a rather pessimistic estimate */ } /* --------------------------------------------------------------------- */ static int baycom_chars_in_buffer(struct tty_struct *tty) { int cnt; struct baycom_state *bc; if (!tty) return 0; if (baycom_paranoia_check(bc = tty->driver_data, "chars_in_buffer")) return 0; cnt = bc->tx_buf.wr - bc->tx_buf.rd; if (cnt < 0) cnt += bc->tx_buf.buflen; return cnt; } /* --------------------------------------------------------------------- */ static void baycom_flush_buffer(struct tty_struct *tty) { struct baycom_state *bc; if (!tty) return; if (baycom_paranoia_check(bc = tty->driver_data, "flush_buffer")) return; wake_up_interruptible(&tty->write_wait); if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); } /* --------------------------------------------------------------------- */ static inline void baycom_dealloc_hw(struct baycom_state *bc) { if (!bc || bc->magic != BAYCOM_MAGIC || bc->modem_type == BAYCOM_MODEM_INVALID) return; switch(bc->modem_type) { case BAYCOM_MODEM_SER12: ser12_deallocate_resources(bc); break; case BAYCOM_MODEM_PAR96: par96_deallocate_resources(bc); break; } } /* --------------------------------------------------------------------- */ static int baycom_set_hardware(struct baycom_state *bc, unsigned int modem_type, unsigned int iobase, unsigned int irq, unsigned int options) { int i; if (!bc) return -EINVAL; if (modem_type == BAYCOM_MODEM_SER12) { i = ser12_allocate_resources(iobase, irq, options); if (i < 0) return i; } else if (modem_type == BAYCOM_MODEM_PAR96) { i = par96_allocate_resources(iobase, irq, options); if (i < 0) return i; } else if (modem_type == BAYCOM_MODEM_INVALID) { iobase = irq = options = 0; } else { return -ENXIO; } baycom_dealloc_hw(bc); bc->modem_type = modem_type; bc->iobase = iobase; bc->irq = irq; bc->options = options; i = 0; if (bc->opened > 0) { switch(bc->modem_type) { case BAYCOM_MODEM_SER12: i = ser12_on_open(bc); break; case BAYCOM_MODEM_PAR96: i = par96_on_open(bc); break; } } return i; } /* --------------------------------------------------------------------- */ static int baycom_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) { int i; struct baycom_state *bc; struct baycom_params par; if (!tty) return -EINVAL; if (baycom_paranoia_check(bc = tty->driver_data, "ioctl")) return -EINVAL; switch (cmd) { default: return -ENOIOCTLCMD; case TIOCMGET: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int)); if (i) return i; i = (bc->modem.dcd ? TIOCM_CAR : 0) | (bc->hdlc_tx.ptt ? TIOCM_RTS : 0); put_user(i, (int *) arg); return 0; case BAYCOMCTL_GETDCD: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned char)); if (!i) put_user(bc->modem.dcd, (unsigned char *) arg); return i; case BAYCOMCTL_GETPTT: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned char)); if (!i) put_user(bc->hdlc_tx.ptt, (unsigned char *) arg); return i; case BAYCOMCTL_PARAM_TXDELAY: if (arg > 255) return -EINVAL; bc->ch_params.tx_delay = arg; return 0; case BAYCOMCTL_PARAM_PPERSIST: if (arg > 255) return -EINVAL; bc->ch_params.ppersist = arg; return 0; case BAYCOMCTL_PARAM_SLOTTIME: if (arg > 255) return -EINVAL; bc->ch_params.slottime = arg; return 0; case BAYCOMCTL_PARAM_TXTAIL: if (arg > 255) return -EINVAL; bc->ch_params.tx_tail = arg; return 0; case BAYCOMCTL_PARAM_FULLDUP: bc->ch_params.fulldup = arg ? 1 : 0; return 0; case BAYCOMCTL_CALIBRATE: bc->calibrate = arg * ((bc->modem_type == BAYCOM_MODEM_PAR96) ? 600 : 75); return 0; case BAYCOMCTL_GETPARAMS: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(par)); if (i) return i; par.modem_type = bc->modem_type; par.iobase = bc->iobase; par.irq = bc->irq; par.options = bc->options; par.tx_delay = bc->ch_params.tx_delay; par.tx_tail = bc->ch_params.tx_tail; par.slottime = bc->ch_params.slottime; par.ppersist = bc->ch_params.ppersist; par.fulldup = bc->ch_params.fulldup; memcpy_tofs((void *)arg, &par, sizeof(par)); return 0; case BAYCOMCTL_SETPARAMS: if (!suser()) return -EPERM; i = verify_area(VERIFY_READ, (void *) arg, sizeof(par)); if (i) return i; memcpy_fromfs(&par, (void *)arg, sizeof(par)); printk(KERN_INFO "baycom: changing hardware type: modem %u " "iobase 0x%x irq %u options 0x%x\n", par.modem_type, par.iobase, par.irq, par.options); i = baycom_set_hardware(bc, par.modem_type, par.iobase, par.irq, par.options); if (i) return i; bc->ch_params.tx_delay = par.tx_delay; bc->ch_params.tx_tail = par.tx_tail; bc->ch_params.slottime = par.slottime; bc->ch_params.ppersist = par.ppersist; bc->ch_params.fulldup = par.fulldup; return 0; case BAYCOMCTL_GETSTAT: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct baycom_statistics)); if (i) return i; memcpy_tofs((void *)arg, &bc->stat, sizeof(struct baycom_statistics)); return 0; #ifdef BAYCOM_DEBUG case BAYCOMCTL_GETSAMPLES: if (bc->bitbuf_channel.rd == bc->bitbuf_channel.wr) return -EAGAIN; i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned char)); if (!i) { put_user(bc->bitbuf_channel.buffer [bc->bitbuf_channel.rd], (unsigned char *) arg); bc->bitbuf_channel.rd = (bc->bitbuf_channel.rd+1) % sizeof(bc->bitbuf_channel.buffer); } return i; case BAYCOMCTL_GETBITS: if (bc->bitbuf_hdlc.rd == bc->bitbuf_hdlc.wr) return -EAGAIN; i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned char)); if (!i) { put_user(bc->bitbuf_hdlc.buffer[bc->bitbuf_hdlc.rd], (unsigned char *) arg); bc->bitbuf_hdlc.rd = (bc->bitbuf_hdlc.rd+1) % sizeof(bc->bitbuf_hdlc.buffer); } return i; case BAYCOMCTL_DEBUG1: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned long)); if (!i) put_user((bc->rx_buf.wr-bc->rx_buf.rd) % bc->rx_buf.buflen, (unsigned long *)arg); return i; case BAYCOMCTL_DEBUG2: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned long)); if (!i) put_user(bc->debug_vals.last_intcnt, (unsigned long *)arg); return i; case BAYCOMCTL_DEBUG3: i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(unsigned long)); if (!i) put_user((long)bc->debug_vals.last_pllcorr, (long *)arg); return i; #endif /* BAYCOM_DEBUG */ } } /* --------------------------------------------------------------------- */ int baycom_open(struct tty_struct *tty, struct file * filp) { int line; struct baycom_state *bc; int i; if(!tty) return -ENODEV; line = MINOR(tty->device) - tty->driver.minor_start; if (line < 0 || line >= NR_PORTS) return -ENODEV; bc = baycom_state+line; if (bc->opened > 0) { bc->opened++; MOD_INC_USE_COUNT; return 0; } /* * initialise some variables */ bc->calibrate = 0; /* * allocate the buffer space */ if (bc->rx_buf.buffer) kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen); if (bc->tx_buf.buffer) kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen); bc->rx_buf.buflen = BUFLEN_RX; bc->tx_buf.buflen = BUFLEN_TX; bc->rx_buf.rd = bc->rx_buf.wr = 0; bc->tx_buf.rd = bc->tx_buf.wr = 0; bc->rx_buf.buffer = kmalloc(bc->rx_buf.buflen, GFP_KERNEL); bc->tx_buf.buffer = kmalloc(bc->tx_buf.buflen, GFP_KERNEL); if (!bc->rx_buf.buffer || !bc->tx_buf.buffer) { if (bc->rx_buf.buffer) kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen); if (bc->tx_buf.buffer) kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen); bc->rx_buf.buffer = bc->tx_buf.buffer = NULL; bc->rx_buf.buflen = bc->tx_buf.buflen = 0; return -ENOMEM; } /* * check if the modem type has been set */ switch(bc->modem_type) { case BAYCOM_MODEM_SER12: i = ser12_on_open(bc); break; case BAYCOM_MODEM_PAR96: i = par96_on_open(bc); break; case BAYCOM_MODEM_INVALID: /* * may open even if no hardware specified, in order to * subsequently allow the BAYCOMCTL_SETPARAMS ioctl */ i = 0; break; default: return -ENODEV; } if (i) return i; bc->opened++; MOD_INC_USE_COUNT; tty->driver_data = bc; bc->tty = tty; return 0; } /* --------------------------------------------------------------------- */ static void baycom_close(struct tty_struct *tty, struct file * filp) { struct baycom_state *bc; if(!tty) return; if (baycom_paranoia_check(bc = tty->driver_data, "close")) return; MOD_DEC_USE_COUNT; bc->opened--; if (bc->opened <= 0) { switch(bc->modem_type) { case BAYCOM_MODEM_SER12: ser12_on_close(bc); break; case BAYCOM_MODEM_PAR96: par96_on_close(bc); break; } tty->driver_data = NULL; bc->tty = NULL; bc->opened = 0; /* * free the buffers */ bc->rx_buf.rd = bc->rx_buf.wr = 0; bc->tx_buf.rd = bc->tx_buf.wr = 0; if (bc->rx_buf.buffer) kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen); if (bc->tx_buf.buffer) kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen); bc->rx_buf.buffer = bc->tx_buf.buffer = NULL; bc->rx_buf.buflen = bc->tx_buf.buflen = 0; } } /* --------------------------------------------------------------------- */ /* * And now the modules code and kernel interface. */ static void init_channel(struct baycom_state *bc) { struct access_params dflt_ch_params = { 20, 2, 10, 40, 0 }; if (!bc) return; bc->hdlc_rx.rx_state = 0; bc->hdlc_tx.tx_state = bc->hdlc_tx.numflags = 0; bc->hdlc_tx.bitstream = 0; bc->hdlc_tx.current_byte = bc->hdlc_tx.ptt = 0; memset(&bc->modem, 0, sizeof(bc->modem)); #ifdef BAYCOM_DEBUG bc->bitbuf_channel.rd = bc->bitbuf_channel.wr = 0; bc->bitbuf_channel.shreg = 0x80; bc->bitbuf_hdlc.rd = bc->bitbuf_hdlc.wr = 0; bc->bitbuf_hdlc.shreg = 0x80; #endif /* BAYCOM_DEBUG */ bc->kiss_decode.dec_state = bc->kiss_decode.escaped = bc->kiss_decode.wr = 0; bc->ch_params = dflt_ch_params; #ifdef BAYCOM_USE_BH bc->tq_receiver.next = bc->tq_transmitter.next = bc->tq_arbitrate.next = NULL; bc->tq_receiver.sync = bc->tq_transmitter.sync = bc->tq_arbitrate.sync = 0; bc->tq_receiver.data = bc->tq_transmitter.data = bc->tq_arbitrate.data = bc; bc->tq_receiver.routine = bh_receiver; bc->tq_transmitter.routine = bh_transmitter; bc->tq_arbitrate.routine = bh_arbitrate; #endif /* BAYCOM_USE_BH */ } static void init_datastructs(void) { int i; for(i = 0; i < NR_PORTS; i++) { struct baycom_state *bc = baycom_state+i; bc->magic = BAYCOM_MAGIC; bc->modem_type = BAYCOM_MODEM_INVALID; bc->iobase = bc->irq = bc->options = bc->opened = 0; bc->tty = NULL; bc->rx_buf.rd = bc->rx_buf.wr = 0; bc->rx_buf.buflen = 0; bc->rx_buf.buffer = NULL; bc->tx_buf.rd = bc->tx_buf.wr = 0; bc->tx_buf.buflen = 0; bc->tx_buf.buffer = NULL; memset(&bc->stat, 0, sizeof(bc->stat)); init_channel(bc); } } int baycom_init(void) { int i, j; /* * initialize the data structures */ init_datastructs(); /* * initialize bottom half handler */ #ifdef BAYCOM_USE_BH init_bh(BAYCOM_BH, baycom_bottom_half); #endif /* BAYCOM_USE_BH */ /* * register the driver as tty driver */ memset(&baycom_driver, 0, sizeof(struct tty_driver)); baycom_driver.magic = TTY_DRIVER_MAGIC; baycom_driver.name = "baycom"; baycom_driver.major = major; baycom_driver.minor_start = 0; baycom_driver.num = NR_PORTS; baycom_driver.type = TTY_DRIVER_TYPE_BAYCOM; baycom_driver.subtype = BAYCOM_TYPE_NORMAL; baycom_driver.init_termios.c_iflag = 0; baycom_driver.init_termios.c_oflag = 0; baycom_driver.init_termios.c_cflag = CS8 | B1200 | CREAD | CLOCAL; baycom_driver.init_termios.c_lflag = 0; baycom_driver.flags = TTY_DRIVER_REAL_RAW; baycom_driver.refcount = &baycom_refcount; baycom_driver.table = baycom_table; baycom_driver.termios = baycom_termios; baycom_driver.termios_locked = baycom_termios_locked; /* * the functions */ baycom_driver.open = baycom_open; baycom_driver.close = baycom_close; baycom_driver.write = baycom_write; baycom_driver.put_char = baycom_put_char; baycom_driver.flush_chars = NULL; baycom_driver.write_room = baycom_write_room; baycom_driver.chars_in_buffer = baycom_chars_in_buffer; baycom_driver.flush_buffer = baycom_flush_buffer; baycom_driver.ioctl = baycom_ioctl; /* * cannot throttle the transmitter on this layer */ baycom_driver.throttle = NULL; baycom_driver.unthrottle = NULL; /* * no special actions on termio changes */ baycom_driver.set_termios = NULL; /* * no XON/XOFF and no hangup on the radio port */ baycom_driver.stop = NULL; baycom_driver.start = NULL; baycom_driver.hangup = NULL; baycom_driver.set_ldisc = NULL; if (tty_register_driver(&baycom_driver)) { printk(KERN_WARNING "baycom: tty_register_driver failed\n"); return -EIO; } for (i = 0; i < NR_PORTS && baycom_ports[i].modem != BAYCOM_MODEM_INVALID; i++) { j = baycom_set_hardware(baycom_state+i, baycom_ports[i].modem, baycom_ports[i].iobase, baycom_ports[i].irq, baycom_ports[i].options); if (j < 0) { const char *s; switch (-j) { case ENXIO: s = "invalid iobase and/or irq"; break; case EACCES: s = "io region already used"; break; case EIO: s = "no uart/lpt port at iobase"; break; case EBUSY: s = "interface already in use"; break; case EINVAL: s = "internal error"; break; default: s = "unknown error"; break; } printk(KERN_WARNING "baycom: modem %u iobase 0x%x " "irq %u: (%i) %s\n", baycom_ports[i].modem, baycom_ports[i].iobase, baycom_ports[i].irq, j, s); } } return 0; } /* --------------------------------------------------------------------- */ #ifdef MODULE int modem = BAYCOM_MODEM_INVALID; int iobase = 0x3f8; int irq = 4; int options = BAYCOM_OPTIONS_SOFTDCD; int init_module(void) { int i; printk(KERN_INFO "baycom: init_module called\n"); baycom_ports[0].modem = modem; baycom_ports[0].iobase = iobase; baycom_ports[0].irq = irq; baycom_ports[0].options = options; baycom_ports[1].modem = BAYCOM_MODEM_INVALID; i = baycom_init(); if (i) return i; printk(KERN_INFO "baycom: version 0.3; " "(C) 1996 by Thomas Sailer HB9JNX, sailer@ife.ee.ethz.ch\n"); return 0; } /* --------------------------------------------------------------------- */ void cleanup_module(void) { int i; printk(KERN_INFO "baycom: cleanup_module called\n"); disable_bh(BAYCOM_BH); if (tty_unregister_driver(&baycom_driver)) printk(KERN_WARNING "baycom: failed to unregister tty " "driver\n"); for(i = 0; i < NR_PORTS; i++) { struct baycom_state *bc = baycom_state+i; if (bc->magic != BAYCOM_MAGIC) printk(KERN_ERR "baycom: invalid magic in " "cleanup_module\n"); else { baycom_dealloc_hw(bc); /* * free the buffers */ bc->rx_buf.rd = bc->rx_buf.wr = 0; bc->tx_buf.rd = bc->tx_buf.wr = 0; if (bc->rx_buf.buffer) kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen); if (bc->tx_buf.buffer) kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen); bc->rx_buf.buffer = bc->tx_buf.buffer = NULL; bc->rx_buf.buflen = bc->tx_buf.buflen = 0; } } } #else /* MODULE */ /* --------------------------------------------------------------------- */ /* * format: baycom=modem,io,irq,options[,modem,io,irq,options] * modem=1: ser12, modem=2: par96 * options=0: hardware DCD, options=1: software DCD */ void baycom_setup(char *str, int *ints) { int i; for (i = 0; i < NR_PORTS; i++) if (ints[0] >= 4*i+4) { baycom_ports[i].modem = ints[4*i+1]; baycom_ports[i].iobase = ints[4*i+2]; baycom_ports[i].irq = ints[4*i+3]; baycom_ports[i].options = ints[4*i+4]; } else baycom_ports[i].modem = BAYCOM_MODEM_INVALID; } #endif /* MODULE */ /* --------------------------------------------------------------------- */