epca.c

来自「linux 内核源代码」· C语言 代码 · 共 2,328 行 · 第 1/5 页

C
2,328
字号
			ch->m_dcd = 0x80;			ch->m_dsr = 0x20;			ch->m_cts = 0x10;			ch->m_ri  = 0x40;			ch->m_dtr = 0x01;			break;		case PCXE:		case PCXEVE:		case PCXI:		case PC64XE:			ch->m_rts = 0x02;			ch->m_dcd = 0x08;			ch->m_dsr = 0x10;			ch->m_cts = 0x20;			ch->m_ri  = 0x40;			ch->m_dtr = 0x80;			break;		}		if (boards[crd].altpin) {			ch->dsr = ch->m_dcd;			ch->dcd = ch->m_dsr;			ch->digiext.digi_flags |= DIGI_ALTPIN;		} else {			ch->dcd = ch->m_dcd;			ch->dsr = ch->m_dsr;		}		ch->boardnum   = crd;		ch->channelnum = i;		ch->magic      = EPCA_MAGIC;		ch->tty        = NULL;		if (shrinkmem) {			fepcmd(ch, SETBUFFER, 32, 0, 0, 0);			shrinkmem = 0;		}		tseg = readw(&bc->tseg);		rseg = readw(&bc->rseg);		switch (bd->type) {		case PCIXEM:		case PCIXRJ:		case PCIXR:			/* Cover all the 2MEG cards */			ch->txptr = memaddr + ((tseg << 4) & 0x1fffff);			ch->rxptr = memaddr + ((rseg << 4) & 0x1fffff);			ch->txwin = FEPWIN | (tseg >> 11);			ch->rxwin = FEPWIN | (rseg >> 11);			break;		case PCXEM:		case EISAXEM:			/* Cover all the 32K windowed cards */			/* Mask equal to window size - 1 */			ch->txptr = memaddr + ((tseg << 4) & 0x7fff);			ch->rxptr = memaddr + ((rseg << 4) & 0x7fff);			ch->txwin = FEPWIN | (tseg >> 11);			ch->rxwin = FEPWIN | (rseg >> 11);			break;		case PCXEVE:		case PCXE:			ch->txptr = memaddr + (((tseg - bd->memory_seg) << 4) & 0x1fff);			ch->txwin = FEPWIN | ((tseg - bd->memory_seg) >> 9);			ch->rxptr = memaddr + (((rseg - bd->memory_seg) << 4) & 0x1fff);			ch->rxwin = FEPWIN | ((rseg - bd->memory_seg) >>9 );			break;		case PCXI:		case PC64XE:			ch->txptr = memaddr + ((tseg - bd->memory_seg) << 4);			ch->rxptr = memaddr + ((rseg - bd->memory_seg) << 4);			ch->txwin = ch->rxwin = 0;			break;		}		ch->txbufhead = 0;		ch->txbufsize = readw(&bc->tmax) + 1;		ch->rxbufhead = 0;		ch->rxbufsize = readw(&bc->rmax) + 1;		lowwater = ch->txbufsize >= 2000 ? 1024 : (ch->txbufsize / 2);		/* Set transmitter low water mark */		fepcmd(ch, STXLWATER, lowwater, 0, 10, 0);		/* Set receiver low water mark */		fepcmd(ch, SRXLWATER, (ch->rxbufsize / 4), 0, 10, 0);		/* Set receiver high water mark */		fepcmd(ch, SRXHWATER, (3 * ch->rxbufsize / 4), 0, 10, 0);		writew(100, &bc->edelay);		writeb(1, &bc->idata);		ch->startc  = readb(&bc->startc);		ch->stopc   = readb(&bc->stopc);		ch->startca = readb(&bc->startca);		ch->stopca  = readb(&bc->stopca);		ch->fepcflag = 0;		ch->fepiflag = 0;		ch->fepoflag = 0;		ch->fepstartc = 0;		ch->fepstopc = 0;		ch->fepstartca = 0;		ch->fepstopca = 0;		ch->close_delay = 50;		ch->count = 0;		ch->blocked_open = 0;		init_waitqueue_head(&ch->open_wait);		init_waitqueue_head(&ch->close_wait);		spin_unlock_irqrestore(&epca_lock, flags);	}	printk(KERN_INFO	        "Digi PC/Xx Driver V%s:  %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n",	        VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);	memwinoff(bd, 0);}static void epcapoll(unsigned long ignored){	unsigned long flags;	int crd;	volatile unsigned int head, tail;	struct channel *ch;	struct board_info *bd;	/*	 * This routine is called upon every timer interrupt. Even though the	 * Digi series cards are capable of generating interrupts this method	 * of non-looping polling is more efficient. This routine checks for	 * card generated events (Such as receive data, are transmit buffer	 * empty) and acts on those events.	 */	for (crd = 0; crd < num_cards; crd++) {		bd = &boards[crd];		ch = card_ptr[crd];		if ((bd->status == DISABLED) || digi_poller_inhibited)			continue;		/*		 * assertmemoff is not needed here; indeed it is an empty		 * subroutine. It is being kept because future boards may need		 * this as well as some legacy boards.		 */		spin_lock_irqsave(&epca_lock, flags);		assertmemoff(ch);		globalwinon(ch);		/*		 * In this case head and tail actually refer to the event queue		 * not the transmit or receive queue.		 */		head = readw(&ch->mailbox->ein);		tail = readw(&ch->mailbox->eout);		/* If head isn't equal to tail we have an event */		if (head != tail)			doevent(crd);		memoff(ch);		spin_unlock_irqrestore(&epca_lock, flags);	} /* End for each card */	mod_timer(&epca_timer, jiffies + (HZ / 25));}static void doevent(int crd){	void __iomem *eventbuf;	struct channel *ch, *chan0;	static struct tty_struct *tty;	struct board_info *bd;	struct board_chan __iomem *bc;	unsigned int tail, head;	int event, channel;	int mstat, lstat;	/*	 * This subroutine is called by epcapoll when an event is detected	 * in the event queue. This routine responds to those events.	 */	bd = &boards[crd];	chan0 = card_ptr[crd];	epcaassert(chan0 <= &digi_channels[nbdevs - 1], "ch out of range");	assertgwinon(chan0);	while ((tail = readw(&chan0->mailbox->eout)) != (head = readw(&chan0->mailbox->ein))) { /* Begin while something in event queue */		assertgwinon(chan0);		eventbuf = bd->re_map_membase + tail + ISTART;		/* Get the channel the event occurred on */		channel = readb(eventbuf);		/* Get the actual event code that occurred */		event = readb(eventbuf + 1);		/*		 * The two assignments below get the current modem status		 * (mstat) and the previous modem status (lstat). These are		 * useful becuase an event could signal a change in modem		 * signals itself.		 */		mstat = readb(eventbuf + 2);		lstat = readb(eventbuf + 3);		ch = chan0 + channel;		if ((unsigned)channel >= bd->numports || !ch)  {			if (channel >= bd->numports)				ch = chan0;			bc = ch->brdchan;			goto next;		}		if ((bc = ch->brdchan) == NULL)			goto next;		if (event & DATA_IND)  { /* Begin DATA_IND */			receive_data(ch);			assertgwinon(ch);		} /* End DATA_IND */		/* else *//* Fix for DCD transition missed bug */		if (event & MODEMCHG_IND) {			/* A modem signal change has been indicated */			ch->imodem = mstat;			if (ch->asyncflags & ASYNC_CHECK_CD) {				if (mstat & ch->dcd)  /* We are now receiving dcd */					wake_up_interruptible(&ch->open_wait);				else					pc_sched_event(ch, EPCA_EVENT_HANGUP); /* No dcd; hangup */			}		}		tty = ch->tty;		if (tty) {			if (event & BREAK_IND) {				/* A break has been indicated */				tty_insert_flip_char(tty, 0, TTY_BREAK);				tty_schedule_flip(tty);			} else if (event & LOWTX_IND)  {				if (ch->statusflags & LOWWAIT) {					ch->statusflags &= ~LOWWAIT;					tty_wakeup(tty);				}			} else if (event & EMPTYTX_IND) {				/* This event is generated by setup_empty_event */				ch->statusflags &= ~TXBUSY;				if (ch->statusflags & EMPTYWAIT) {					ch->statusflags &= ~EMPTYWAIT;					tty_wakeup(tty);				}			}		}	next:		globalwinon(ch);		BUG_ON(!bc);		writew(1, &bc->idata);		writew((tail + 4) & (IMAX - ISTART - 4), &chan0->mailbox->eout);		globalwinon(chan0);	} /* End while something in event queue */}static void fepcmd(struct channel *ch, int cmd, int word_or_byte,                   int byte2, int ncmds, int bytecmd){	unchar __iomem *memaddr;	unsigned int head, cmdTail, cmdStart, cmdMax;	long count;	int n;	/* This is the routine in which commands may be passed to the card. */	if (ch->board->status == DISABLED)		return;	assertgwinon(ch);	/* Remember head (As well as max) is just an offset not a base addr */	head = readw(&ch->mailbox->cin);	/* cmdStart is a base address */	cmdStart = readw(&ch->mailbox->cstart);	/*	 * We do the addition below because we do not want a max pointer	 * relative to cmdStart. We want a max pointer that points at the	 * physical end of the command queue.	 */	cmdMax = (cmdStart + 4 + readw(&ch->mailbox->cmax));	memaddr = ch->board->re_map_membase;	if (head >= (cmdMax - cmdStart) || (head & 03))  {		printk(KERN_ERR "line %d: Out of range, cmd = %x, head = %x\n", __LINE__,  cmd, head);		printk(KERN_ERR "line %d: Out of range, cmdMax = %x, cmdStart = %x\n", __LINE__,  cmdMax, cmdStart);		return;	}	if (bytecmd)  {		writeb(cmd, memaddr + head + cmdStart + 0);		writeb(ch->channelnum,  memaddr + head + cmdStart + 1);		/* Below word_or_byte is bits to set */		writeb(word_or_byte,  memaddr + head + cmdStart + 2);		/* Below byte2 is bits to reset */		writeb(byte2, memaddr + head + cmdStart + 3);	}  else {		writeb(cmd, memaddr + head + cmdStart + 0);		writeb(ch->channelnum,  memaddr + head + cmdStart + 1);		writeb(word_or_byte,  memaddr + head + cmdStart + 2);	}	head = (head + 4) & (cmdMax - cmdStart - 4);	writew(head, &ch->mailbox->cin);	count = FEPTIMEOUT;	for (;;) {		count--;		if (count == 0)  {			printk(KERN_ERR "<Error> - Fep not responding in fepcmd()\n");			return;		}		head = readw(&ch->mailbox->cin);		cmdTail = readw(&ch->mailbox->cout);		n = (head - cmdTail) & (cmdMax - cmdStart - 4);		/*		 * Basically this will break when the FEP acknowledges the		 * command by incrementing cmdTail (Making it equal to head).		 */		if (n <= ncmds * (sizeof(short) * 4))			break;	}}/* * Digi products use fields in their channels structures that are very similar * to the c_cflag and c_iflag fields typically found in UNIX termios * structures. The below three routines allow mappings between these hardware * "flags" and their respective Linux flags. */static unsigned termios2digi_h(struct channel *ch, unsigned cflag){	unsigned res = 0;	if (cflag & CRTSCTS) {		ch->digiext.digi_flags |= (RTSPACE | CTSPACE);		res |= ((ch->m_cts) | (ch->m_rts));	}	if (ch->digiext.digi_flags & RTSPACE)		res |= ch->m_rts;	if (ch->digiext.digi_flags & DTRPACE)		res |= ch->m_dtr;	if (ch->digiext.digi_flags & CTSPACE)		res |= ch->m_cts;	if (ch->digiext.digi_flags & DSRPACE)		res |= ch->dsr;	if (ch->digiext.digi_flags & DCDPACE)		res |= ch->dcd;	if (res & (ch->m_rts))		ch->digiext.digi_flags |= RTSPACE;	if (res & (ch->m_cts))		ch->digiext.digi_flags |= CTSPACE;	return res;}static unsigned termios2digi_i(struct channel *ch, unsigned iflag){	unsigned res = iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK |	                        INPCK | ISTRIP|IXON|IXANY|IXOFF);	if (ch->digiext.digi_flags & DIGI_AIXON)		res |= IAIXON;	return res;}static unsigned termios2digi_c(struct channel *ch, unsigned cflag){	unsigned res = 0;	if (cflag & CBAUDEX) {		ch->digiext.digi_flags |= DIGI_FAST;		/*		 * HUPCL bit is used by FEP to indicate fast baud table is to		 * be used.		 */		res |= FEP_HUPCL;	} else		ch->digiext.digi_flags &= ~DIGI_FAST;	/*	 * CBAUD has bit position 0x1000 set these days to indicate Linux	 * baud rate remap. Digi hardware can't handle the bit assignment.	 * (We use a different bit assignment for high speed.). Clear this	 * bit out.	 */	res |= cflag & ((CBAUD ^ CBAUDEX) | PARODD | PARENB | CSTOPB | CSIZE);	/*	 * This gets a little confusing. The Digi cards have their own	 * representation of c_cflags controling baud rate. For the most part	 * this is identical to the Linux implementation. However; Digi	 * supports one rate (76800) that Linux doesn't. This means that the	 * c_cflag entry that would normally mean 76800 for Digi actually means	 * 115200 under Linux. Without the below mapping, a stty 115200 would	 * only drive the board at 76800. Since the rate 230400 is also found	 * after 76800, the same problem afflicts us when we choose a rate of	 * 230400. Without the below modificiation stty 230400 would actually	 * give us 115200.	 *	 * There are two additional differences. The Linux value for CLOCAL	 * (0x800; 0004000) has no meaning to the Digi hardware. Also in later	 * releases of Linux; the CBAUD define has CBAUDEX (0x1000; 0010000)	 * ored into it (CBAUD = 0x100f as opposed to 0xf). CBAUDEX should be	 * checked for a screened out prior to termios2digi_c returning. Since	 * CLOCAL isn't used by the board this can be ignored as long as the	 * returned value is used only by Digi hardware.	 */	if (cflag & CBAUDEX) {		/*		 * The below code is trying to guarantee that only baud rates		 * 115200 and 230400 are remapped. We use exclusive or because		 * the various baud rates share common bit positions and		 * therefore can't be tested for easily.		 */		if ((!((cflag & 0x7) ^ (B115200 & ~CBAUDEX))) ||		    (!((cflag & 0x7) ^ (B230400 & ~CBAUDEX))))			res += 1;	}	return res;}/* Caller must hold the locks */static void epcaparam(struct tty_struct *tty, struct channel *ch){	unsigned int cmdHead;	struct ktermios *ts;	struct board_chan __iomem *bc;	unsigned mval, hflow, cflag, iflag;	bc = ch->brdchan;	epcaassert(bc !=0, "bc out of range");	assertgwinon(ch);	ts = tty->termios;	if ((ts->c_cflag & CBAUD) == 0)  { /* Begin CBAUD detected */		cmdHead = readw(&bc->rin);		writew(cmdHead, &bc->rout);		cmdHead = readw(&bc->tin);		/* Changing baud in mid-stream transmission can be wonderful */		/*		 * Flush current transmit buffer by setting cmdTail pointer		 * (tout) to cmdHead pointer (tin). Hopefully the transmit		 * buffer is empty.		 */		fepcmd(ch, STOUT, (unsigned) cmdHead, 0, 0, 0);		mval = 0;	} else { /* Begin CBAUD not detected */		/*		 * c_cflags have changed but that change had nothing to do with		 * BAUD. Propagate the change to the card.		 */		cflag = termios2digi_c(ch, ts->c_cflag);		if (cflag != ch->fepcflag)  {			ch->fepcflag = cflag;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?