📄 pcxx.c
字号:
/* * linux/drivers/char/pcxe.c * * Written by Troy De Jongh, November, 1994 * * Copyright (C) 1994,1995 Troy De Jongh * This software may be used and distributed according to the terms * of the GNU Public License. * * This driver is for the DigiBoard PC/Xe and PC/Xi line of products. * * This driver does NOT support DigiBoard's fastcook FEP option and * does not support the transparent print (i.e. digiprint) option. * * This Driver is currently maintained by Christoph Lameter (clameter@fuller.edu) * Please contact the mailing list for problems first. * * Sources of Information: * 1. The Linux Digiboard Page at http://private.fuller.edu/clameter/digi.html * 2. The Linux Digiboard Mailing list at digiboard@list.fuller.edu * (Simply write a message to introduce yourself to subscribe) * * 1.5.2 Fall 1995 Bug fixes by David Nugent * 1.5.3 March 9, 1996 Christoph Lameter: Fixed 115.2K Support. Memory * allocation harmonized with 1.3.X Series. * 1.5.4 March 30, 1996 Christoph Lameter: Fixup for 1.3.81. Use init_bh * instead of direct assignment to kernel arrays. * 1.5.5 April 5, 1996 Major device numbers corrected. * Mike McLagan<mike.mclagan@linux.org>: Add setup * variable handling, instead of using the old pcxxconfig.h * 1.5.6 April 16, 1996 Christoph Lameter: Pointer cleanup, macro cleanup. * Call out devices changed to /dev/cudxx. * 1.5.7 July 22, 1996 Martin Mares: CLOCAL fix, pcxe_table clearing. * David Nugent: Bug in pcxe_open. * Brian J. Murrell: Modem Control fixes, Majors correctly assigned * 1.6.1 April 6, 1997 Bernhard Kaindl: fixed virtual memory access for 2.1 * i386-kernels and use on other archtitectures, Allowing use * as module, added module parameters, added switch to enable * verbose messages to assist user during card configuration. * Currently only tested on a PC/Xi card, but should work on Xe * and Xeve also. * */#undef SPEED_HACK/* If you define SPEED_HACK then you get the following Baudrate translation 19200 = 57600 38400 = 115K The driver supports the native 57.6K and 115K Baudrates under Linux, but some distributions like Slackware 3.0 don't like these high baudrates.*/#include <linux/module.h>#include <linux/mm.h>#include <linux/ioport.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/ptrace.h>#include <linux/delay.h>#include <linux/serial.h>#include <linux/tty_driver.h>#include <linux/malloc.h>#include <linux/init.h>#include <linux/version.h>#ifndef MODULE#include <linux/ctype.h> /* We only need it for parsing the "digi="-line */#endif#include <asm/system.h>#include <asm/io.h>#include <asm/uaccess.h>#include <asm/bitops.h>#define VERSION "1.6.1"#include "digi.h"#include "fep.h"#include "pcxx.h"#include "digi_fep.h"#include "digi_bios.h"/* * Define one default setting if no digi= config line is used. * Default is altpin = disabled, 16 ports, I/O 200h, Memory 0D0000h */static struct board_info boards[MAX_DIGI_BOARDS] = { {/* Board is enabled */ ENABLED,/* Type is auto-detected */ 0,/* altping is disabled */ DISABLED,/* number of ports = 16 */ 16,/* io address is 0x200 */ 0x200,/* card memory at 0xd0000 */ 0xd0000,/* first minor device no. */ 0} }; static int verbose = 0;static int debug = 0;#ifdef MODULE/* Variables for insmod */static int io[] = {0, 0, 0, 0};static int membase[] = {0, 0, 0, 0};static int memsize[] = {0, 0, 0, 0};static int altpin[] = {0, 0, 0, 0};static int numports[] = {0, 0, 0, 0};# if (LINUX_VERSION_CODE > 0x020111)MODULE_AUTHOR("Bernhard Kaindl");MODULE_DESCRIPTION("Digiboard PC/X{i,e,eve} driver");MODULE_PARM(verbose, "i");MODULE_PARM(debug, "i");MODULE_PARM(io, "1-4i");MODULE_PARM(membase, "1-4i");MODULE_PARM(memsize, "1-4i");MODULE_PARM(altpin, "1-4i");MODULE_PARM(numports, "1-4i");# endif#endif MODULEstatic int numcards = 1;static int nbdevs = 0; static struct channel *digi_channels;static struct tty_struct **pcxe_table;static struct termios **pcxe_termios;static struct termios **pcxe_termios_locked; int pcxx_ncook=sizeof(pcxx_cook);int pcxx_nbios=sizeof(pcxx_bios);#define MIN(a,b) ((a) < (b) ? (a) : (b))#define pcxxassert(x, msg) if(!(x)) pcxx_error(__LINE__, msg)#define FEPTIMEOUT 200000 #define SERIAL_TYPE_NORMAL 1#define SERIAL_TYPE_CALLOUT 2#define PCXE_EVENT_HANGUP 1struct tty_driver pcxe_driver;struct tty_driver pcxe_callout;static int pcxe_refcount;DECLARE_TASK_QUEUE(tq_pcxx);static void pcxxpoll(void);static void pcxxdelay(int);static void fepcmd(struct channel *, int, int, int, int, int);static void pcxe_put_char(struct tty_struct *, unsigned char);static void pcxe_flush_chars(struct tty_struct *);static void pcxx_error(int, char *);static void pcxe_close(struct tty_struct *, struct file *);static int pcxe_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);static void pcxe_set_termios(struct tty_struct *, struct termios *);static int pcxe_write(struct tty_struct *, int, const unsigned char *, int);static int pcxe_write_room(struct tty_struct *);static int pcxe_chars_in_buffer(struct tty_struct *);static void pcxe_flush_buffer(struct tty_struct *);static void doevent(int);static void receive_data(struct channel *);static void pcxxparam(struct tty_struct *, struct channel *ch);static void do_softint(void *);static inline void pcxe_sched_event(struct channel *, int);static void do_pcxe_bh(void);static void pcxe_start(struct tty_struct *);static void pcxe_stop(struct tty_struct *);static void pcxe_throttle(struct tty_struct *);static void pcxe_unthrottle(struct tty_struct *);static void digi_send_break(struct channel *ch, int msec);static void shutdown(struct channel *);static void setup_empty_event(struct tty_struct *tty, struct channel *ch);static inline void memwinon(struct board_info *b, unsigned int win);static inline void memwinoff(struct board_info *b, unsigned int win);static inline void globalwinon(struct channel *ch);static inline void rxwinon(struct channel *ch);static inline void txwinon(struct channel *ch);static inline void memoff(struct channel *ch);static inline void assertgwinon(struct channel *ch);static inline void assertmemoff(struct channel *ch);#define TZ_BUFSZ 4096/* function definitions */#ifdef MODULE/* * pcxe_init() is our init_module(): */#define pcxe_init init_modulevoid cleanup_module(void);/*****************************************************************************/void cleanup_module(){ unsigned long flags; int crd, i; int e1, e2; struct board_info *bd; struct channel *ch; printk(KERN_NOTICE "Unloading PC/Xx version %s\n", VERSION); save_flags(flags); cli(); timer_active &= ~(1 << DIGI_TIMER); timer_table[DIGI_TIMER].fn = NULL; timer_table[DIGI_TIMER].expires = 0; remove_bh(DIGI_BH); if ((e1 = tty_unregister_driver(&pcxe_driver))) printk("SERIAL: failed to unregister serial driver (%d)\n", e1); if ((e2 = tty_unregister_driver(&pcxe_callout))) printk("SERIAL: failed to unregister callout driver (%d)\n",e2); for(crd=0; crd < numcards; crd++) { bd = &boards[crd]; ch = digi_channels+bd->first_minor; for(i=0; i < bd->numports; i++, ch++) { kfree(ch->tmp_buf); } release_region(bd->port, 4); } kfree(digi_channels); kfree(pcxe_termios_locked); kfree(pcxe_termios); kfree(pcxe_table); restore_flags(flags);}#endifstatic inline struct channel *chan(register struct tty_struct *tty){ if (tty) { register struct channel *ch=(struct channel *)tty->driver_data; if (ch >= digi_channels && ch < digi_channels+nbdevs) { if (ch->magic==PCXX_MAGIC) return ch; } } return NULL;}/* These inline routines are to turn board memory on and off */static inline void memwinon(struct board_info *b, unsigned int win){ if(b->type == PCXEVE) outb_p(FEPWIN|win, b->port+1); else outb_p(inb(b->port)|FEPMEM, b->port);}static inline void memwinoff(struct board_info *b, unsigned int win){ outb_p(inb(b->port)&~FEPMEM, b->port); if(b->type == PCXEVE) outb_p(0, b->port + 1);}static inline void globalwinon(struct channel *ch){ if(ch->board->type == PCXEVE) outb_p(FEPWIN, ch->board->port+1); else outb_p(FEPMEM, ch->board->port);}static inline void rxwinon(struct channel *ch){ if(ch->rxwin == 0) outb_p(FEPMEM, ch->board->port); else outb_p(ch->rxwin, ch->board->port+1);}static inline void txwinon(struct channel *ch){ if(ch->txwin == 0) outb_p(FEPMEM, ch->board->port); else outb_p(ch->txwin, ch->board->port+1);}static inline void memoff(struct channel *ch){ outb_p(0, ch->board->port); if(ch->board->type == PCXEVE) outb_p(0, ch->board->port+1);}static inline void assertgwinon(struct channel *ch){ if(ch->board->type != PCXEVE) pcxxassert(inb(ch->board->port) & FEPMEM, "Global memory off");}static inline void assertmemoff(struct channel *ch){ if(ch->board->type != PCXEVE) pcxxassert(!(inb(ch->board->port) & FEPMEM), "Memory on");}static inline void pcxe_sched_event(struct channel *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_pcxx); mark_bh(DIGI_BH);}static void pcxx_error(int line, char *msg){ printk("pcxx_error (DigiBoard): line=%d %s\n", line, msg);}static int pcxx_waitcarrier(struct tty_struct *tty,struct file *filp,struct channel *info){ struct wait_queue wait = { current, NULL }; int retval = 0; int do_clocal = 0; if (info->asyncflags & ASYNC_CALLOUT_ACTIVE) { if (info->normal_termios.c_cflag & CLOCAL) do_clocal = 1; } else { if (tty->termios->c_cflag & CLOCAL) do_clocal = 1; } /* * Block waiting for the carrier detect and the line to become free */ retval = 0; add_wait_queue(&info->open_wait, &wait); info->count--; info->blocked_open++; for (;;) { cli(); if ((info->asyncflags & ASYNC_CALLOUT_ACTIVE) == 0) { globalwinon(info); info->omodem |= DTR|RTS; fepcmd(info, SETMODEM, DTR|RTS, 0, 10, 1); memoff(info); } sti(); current->state = TASK_INTERRUPTIBLE; if(tty_hung_up_p(filp) || (info->asyncflags & ASYNC_INITIALIZED) == 0) { if(info->asyncflags & ASYNC_HUP_NOTIFY) retval = -EAGAIN; else retval = -ERESTARTSYS; break; } if ((info->asyncflags & ASYNC_CALLOUT_ACTIVE) == 0 && (info->asyncflags & ASYNC_CLOSING) == 0 && (do_clocal || (info->imodem & info->dcd))) break; if(signal_pending(current)) { retval = -ERESTARTSYS; break; } schedule(); } current->state = TASK_RUNNING; remove_wait_queue(&info->open_wait, &wait); if(!tty_hung_up_p(filp)) info->count++; info->blocked_open--; return retval;} int pcxe_open(struct tty_struct *tty, struct file * filp){ volatile struct board_chan *bc; struct channel *ch; unsigned long flags; int line; int boardnum; int retval; line = MINOR(tty->device) - tty->driver.minor_start; if(line < 0 || line >= nbdevs) { printk("line out of range in pcxe_open\n"); tty->driver_data = NULL; return(-ENODEV); } for(boardnum=0;boardnum<numcards;boardnum++) if ((line >= boards[boardnum].first_minor) && (line < boards[boardnum].first_minor + boards[boardnum].numports)) break; if(boardnum >= numcards || boards[boardnum].status == DISABLED || (line - boards[boardnum].first_minor) >= boards[boardnum].numports) { tty->driver_data = NULL; /* Mark this device as 'down' */ return(-ENODEV); } ch = digi_channels+line; if(ch->brdchan == 0) { tty->driver_data = NULL; return(-ENODEV); } /* flag the kernel that there is somebody using this guy */ MOD_INC_USE_COUNT; /* * If the device is in the middle of being closed, then block * until it's done, and then try again. */ if(ch->asyncflags & ASYNC_CLOSING) { interruptible_sleep_on(&ch->close_wait); if(ch->asyncflags & ASYNC_HUP_NOTIFY) return -EAGAIN; else return -ERESTARTSYS; } save_flags(flags); cli(); ch->count++; tty->driver_data = ch; ch->tty = tty; if ((ch->asyncflags & ASYNC_INITIALIZED) == 0) { unsigned int head; globalwinon(ch); ch->statusflags = 0; bc=ch->brdchan; ch->imodem = bc->mstat; head = bc->rin; bc->rout = head; ch->tty = tty; pcxxparam(tty,ch); ch->imodem = bc->mstat; bc->idata = 1; ch->omodem = DTR|RTS; fepcmd(ch, SETMODEM, DTR|RTS, 0, 10, 1); memoff(ch); ch->asyncflags |= ASYNC_INITIALIZED; } restore_flags(flags); if(ch->asyncflags & ASYNC_CLOSING) { interruptible_sleep_on(&ch->close_wait); if(ch->asyncflags & ASYNC_HUP_NOTIFY) return -EAGAIN; else return -ERESTARTSYS; } /* * If this is a callout device, then just make sure the normal * device isn't being used. */ if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) { if (ch->asyncflags & ASYNC_NORMAL_ACTIVE) return -EBUSY; if (ch->asyncflags & ASYNC_CALLOUT_ACTIVE) { if ((ch->asyncflags & ASYNC_SESSION_LOCKOUT) && (ch->session != current->session)) return -EBUSY; if((ch->asyncflags & ASYNC_PGRP_LOCKOUT) && (ch->pgrp != current->pgrp)) return -EBUSY; } ch->asyncflags |= ASYNC_CALLOUT_ACTIVE; } else { if (filp->f_flags & O_NONBLOCK) { if(ch->asyncflags & ASYNC_CALLOUT_ACTIVE) return -EBUSY; } else { /* this has to be set in order for the "block until * CD" code to work correctly. i'm not sure under * what circumstances asyncflags should be set to * ASYNC_NORMAL_ACTIVE though * brian@ilinx.com */ ch->asyncflags |= ASYNC_NORMAL_ACTIVE; if ((retval = pcxx_waitcarrier(tty, filp, ch)) != 0) return retval; } ch->asyncflags |= ASYNC_NORMAL_ACTIVE; } save_flags(flags); cli(); if((ch->count == 1) && (ch->asyncflags & ASYNC_SPLIT_TERMIOS)) { if(tty->driver.subtype == SERIAL_TYPE_NORMAL) *tty->termios = ch->normal_termios; else *tty->termios = ch->callout_termios; globalwinon(ch); pcxxparam(tty,ch); memoff(ch); } ch->session = current->session; ch->pgrp = current->pgrp; restore_flags(flags); return 0;} static void shutdown(struct channel *info){ unsigned long flags; volatile struct board_chan *bc; struct tty_struct *tty; if (!(info->asyncflags & ASYNC_INITIALIZED)) return; save_flags(flags); cli(); globalwinon(info); bc = info->brdchan; if(bc) bc->idata = 0; tty = info->tty; /* * If we're a modem control device and HUPCL is on, drop RTS & DTR. */ if(tty->termios->c_cflag & HUPCL) { info->omodem &= ~(RTS|DTR); fepcmd(info, SETMODEM, 0, DTR|RTS, 10, 1); } memoff(info); info->asyncflags &= ~ASYNC_INITIALIZED; restore_flags(flags);}static void pcxe_close(struct tty_struct * tty, struct file * filp){ struct channel *info; if ((info=chan(tty))!=NULL) { unsigned long flags; save_flags(flags); cli(); if(tty_hung_up_p(filp)) { /* flag that somebody is done with this module */ MOD_DEC_USE_COUNT; restore_flags(flags); return; } /* this check is in serial.c, it won't hurt to do it here too */ if ((tty->count == 1) && (info->count != 1)) { /* * Uh, oh. tty->count is 1, which means that the tty * structure will be freed. Info->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. */ printk("pcxe_close: bad serial port count; tty->count is 1, info->count is %d\n", info->count); info->count = 1; } if (info->count-- > 1) { restore_flags(flags); MOD_DEC_USE_COUNT; return; } if (info->count < 0) { info->count = 0; } info->asyncflags |= ASYNC_CLOSING; /* * Save the termios structure, since this port may have * separate termios for callout and dialin. */ if(info->asyncflags & ASYNC_NORMAL_ACTIVE) info->normal_termios = *tty->termios; if(info->asyncflags & ASYNC_CALLOUT_ACTIVE) info->callout_termios = *tty->termios; tty->closing = 1; if(info->asyncflags & ASYNC_INITIALIZED) { setup_empty_event(tty,info); tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */ } if(tty->driver.flush_buffer) tty->driver.flush_buffer(tty); if(tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); shutdown(info); tty->closing = 0; info->event = 0; info->tty = NULL;#ifndef MODULE/* ldiscs[] is not available in a MODULE** worth noting that while I'm not sure what this hunk of code is supposed** to do, it is not present in the serial.c driver. Hmmm. If you know,** please send me a note. brian@ilinx.com** Don't know either what this is supposed to do clameter@waterf.org.*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -