📄 uart.c
字号:
#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) printk("CTS tx start...");#endif info->tty->hw_stopped = 0; info->IER |= UART_IER_THRI; serial_out(info, UART_IER, info->IER); rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); return; } } else { if (!(status & UART_MSR_CTS)) {#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) printk("CTS tx stop...");#endif info->tty->hw_stopped = 1; info->IER &= ~UART_IER_THRI; serial_out(info, UART_IER, info->IER); } } }}#endif/* * This is the serial driver's interrupt routine for a single port */static void rs_8xx_interrupt(int irq, void * dev_id, struct pt_regs * regs){ u_char events; int idx; ser_info_t *info; volatile smc_t *smcp; volatile scc_t *sccp; info = (ser_info_t *)dev_id; if ((idx = info->state->smc_scc_num) < SCC_NUM_BASE) { smcp = &immr->im_smc[idx]; events = smcp->smc_smce; if (events & SMCM_RX) receive_chars(info); if (events & SMCM_TX) transmit_chars(info); smcp->smc_smce = events; } else { sccp = &immr->im_scc[idx - SCC_IDX_BASE]; events = sccp->scc_scce; if (events & SCCM_RX) receive_chars(info); if (events & SCCM_TX) transmit_chars(info); sccp->scc_scce = events; } #ifdef SERIAL_DEBUG_INTR printk("rs_interrupt_single(%d, %x)...", info->state->smc_scc_num, events);#endif#ifdef modem_control check_modem_status(info);#endif info->last_active = jiffies;#ifdef SERIAL_DEBUG_INTR printk("end.\n");#endif}/* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- *//* * This routine is used to handle the "bottom half" processing for the * serial driver, known also the "software interrupt" processing. * This processing is done at the kernel interrupt level, after the * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This * is where time-consuming activities which can not be done in the * interrupt driver proper are done; the interrupt driver schedules * them using rs_sched_event(), and they get done here. */static void do_serial_bh(void){ run_task_queue(&tq_serial);}static void do_softint(void *private_){ ser_info_t *info = (ser_info_t *) private_; struct tty_struct *tty; tty = info->tty; if (!tty) return; if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); }}/* * This routine is called from the scheduler tqueue when the interrupt * routine has signalled that a hangup has occurred. The path of * hangup processing is: * * serial interrupt routine -> (scheduler tqueue) -> * do_serial_hangup() -> tty->hangup() -> rs_hangup() * */static void do_serial_hangup(void *private_){ struct async_struct *info = (struct async_struct *) private_; struct tty_struct *tty; tty = info->tty; if (tty) tty_hangup(tty); MOD_DEC_USE_COUNT;}/*static void rs_8xx_timer(void){ printk("rs_8xx_timer\n");}*/static int startup(ser_info_t *info){ unsigned long flags; int retval=0; int idx; struct serial_state *state= info->state; volatile smc_t *smcp; volatile scc_t *sccp; volatile smc_uart_t *up; volatile scc_uart_t *scup; save_flags(flags); cli(); if (info->flags & ASYNC_INITIALIZED) { goto errout; }#ifdef maybe if (!state->port || !state->type) { if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); goto errout; }#endif#ifdef SERIAL_DEBUG_OPEN printk("starting up ttys%d (irq %d)...", info->line, state->irq);#endif#ifdef modem_control info->MCR = 0; if (info->tty->termios->c_cflag & CBAUD) info->MCR = UART_MCR_DTR | UART_MCR_RTS;#endif if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); /* * and set the speed of the serial port */ change_speed(info); if ((idx = info->state->smc_scc_num) < SCC_NUM_BASE) { smcp = &immr->im_smc[idx]; /* Enable interrupts and I/O. */ smcp->smc_smcm |= (SMCM_RX | SMCM_TX); smcp->smc_smcmr |= (SMCMR_REN | SMCMR_TEN); /* We can tune the buffer length and idle characters * to take advantage of the entire incoming buffer size. * If mrblr is something other than 1, maxidl has to be * non-zero or we never get an interrupt. The maxidl * is the number of character times we wait after reception * of the last character before we decide no more characters * are coming. */ up = (smc_uart_t *)&immr->im_dprambase[state->port];#if 0 up->smc_mrblr = 1; /* receive buffer length */ up->smc_maxidl = 0; /* wait forever for next char */#else up->smc_mrblr = RX_BUF_SIZE; up->smc_maxidl = RX_BUF_SIZE;#endif up->smc_brkcr = 1; /* number of break chars */ } else { sccp = &immr->im_scc[idx - SCC_IDX_BASE]; scup = (scc_uart_t *)&immr->im_dprambase[state->port];#if 0 scup->scc_genscc.scc_mrblr = 1; /* receive buffer length */ scup->scc_maxidl = 0; /* wait forever for next char */#else scup->scc_genscc.scc_mrblr = RX_BUF_SIZE; scup->scc_maxidl = RX_BUF_SIZE;#endif sccp->scc_sccm |= (UART_SCCM_TX | UART_SCCM_RX); sccp->scc_gsmrl |= (SCC_GSMRL_ENR | SCC_GSMRL_ENT); } info->flags |= ASYNC_INITIALIZED; restore_flags(flags); return 0; errout: restore_flags(flags); return retval;}/* * This routine will shutdown a serial port; interrupts are disabled, and * DTR is dropped if the hangup on close termio flag is on. */static void shutdown(ser_info_t * info){ unsigned long flags; struct serial_state *state; int idx; volatile smc_t *smcp; volatile scc_t *sccp; if (!(info->flags & ASYNC_INITIALIZED)) return; state = info->state;#ifdef SERIAL_DEBUG_OPEN printk("Shutting down serial port %d (irq %d)....", info->line, state->irq);#endif save_flags(flags); cli(); /* Disable interrupts */ if ((idx = info->state->smc_scc_num) < SCC_NUM_BASE) { smcp = &immr->im_smc[idx]; /* Disable interrupts and I/O. */ smcp->smc_smcm &= ~(SMCM_RX | SMCM_TX);#ifdef CONFIG_SERIAL_CONSOLE /* We can't disable the transmitter if this is the * system console. */ if (idx != CONFIG_SERIAL_CONSOLE_PORT)#endif smcp->smc_smcmr &= ~(SMCMR_REN | SMCMR_TEN); } else { sccp = &immr->im_scc[idx - SCC_IDX_BASE]; sccp->scc_sccm &= ~(UART_SCCM_TX | UART_SCCM_RX); sccp->scc_gsmrl &= ~(SCC_GSMRL_ENR | SCC_GSMRL_ENT); } if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); info->flags &= ~ASYNC_INITIALIZED; restore_flags(flags);}/* * This routine is called to set the UART divisor registers to match * the specified baud rate for a serial port. */static void change_speed(ser_info_t *info){ int baud_rate; unsigned cflag, cval, scval, prev_mode; int i, bits, sbits, idx; unsigned long flags; volatile smc_t *smcp; volatile scc_t *sccp; if (!info->tty || !info->tty->termios) return; cflag = info->tty->termios->c_cflag; /* Character length programmed into the mode register is the * sum of: 1 start bit, number of data bits, 0 or 1 parity bit, * 1 or 2 stop bits, minus 1. * The value 'bits' counts this for us. */ cval = 0; scval = 0; /* byte size and parity */ switch (cflag & CSIZE) { case CS5: bits = 5; break; case CS6: bits = 6; break; case CS7: bits = 7; break; case CS8: bits = 8; break; /* Never happens, but GCC is too dumb to figure it out */ default: bits = 8; break; } sbits = bits - 5; if (cflag & CSTOPB) { cval |= SMCMR_SL; /* Two stops */ scval |= SCU_PMSR_SL; bits++; } if (cflag & PARENB) { cval |= SMCMR_PEN; scval |= SCU_PMSR_PEN; bits++; } if (!(cflag & PARODD)) { cval |= SMCMR_PM_EVEN; scval |= (SCU_PMSR_REVP | SCU_PMSR_TEVP); } /* Determine divisor based on baud rate */ i = cflag & CBAUD; if (i >= (sizeof(baud_table)/sizeof(int))) baud_rate = 9600; else baud_rate = baud_table[i]; info->timeout = (TX_BUF_SIZE*HZ*bits); info->timeout += HZ/50; /* Add .02 seconds of slop */#ifdef modem_control /* CTS flow control flag and modem status interrupts */ info->IER &= ~UART_IER_MSI; if (info->flags & ASYNC_HARDPPS_CD) info->IER |= UART_IER_MSI; if (cflag & CRTSCTS) { info->flags |= ASYNC_CTS_FLOW; info->IER |= UART_IER_MSI; } else info->flags &= ~ASYNC_CTS_FLOW; if (cflag & CLOCAL) info->flags &= ~ASYNC_CHECK_CD; else { info->flags |= ASYNC_CHECK_CD; info->IER |= UART_IER_MSI; } serial_out(info, UART_IER, info->IER);#endif /* * Set up parity check flag */#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) info->read_status_mask = (BD_SC_EMPTY | BD_SC_OV); if (I_INPCK(info->tty)) info->read_status_mask |= BD_SC_FR | BD_SC_PR; if (I_BRKINT(info->tty) || I_PARMRK(info->tty)) info->read_status_mask |= BD_SC_BR; /* * Characters to ignore */ info->ignore_status_mask = 0; if (I_IGNPAR(info->tty)) info->ignore_status_mask |= BD_SC_PR | BD_SC_FR; if (I_IGNBRK(info->tty)) { info->ignore_status_mask |= BD_SC_BR; /* * If we're ignore parity and break indicators, ignore * overruns too. (For real raw support). */ if (I_IGNPAR(info->tty)) info->ignore_status_mask |= BD_SC_OV; } /* * !!! ignore all characters if CREAD is not set */ if ((cflag & CREAD) == 0) info->read_status_mask &= ~BD_SC_EMPTY; save_flags(flags); cli(); /* Start bit has not been added (so don't, because we would just * subtract it later), and we need to add one for the number of * stops bits (there is always at least one). */ bits++; if ((idx = info->state->smc_scc_num) < SCC_NUM_BASE) { smcp = &immr->im_smc[idx]; /* Set the mode register. We want to keep a copy of the * enables, because we want to put them back if they were * present. */ prev_mode = smcp->smc_smcmr; smcp->smc_smcmr = smcr_mk_clen(bits) | cval | SMCMR_SM_UART; smcp->smc_smcmr |= (prev_mode & (SMCMR_REN | SMCMR_TEN)); } else { sccp = &immr->im_scc[idx - SCC_IDX_BASE]; sccp->scc_pmsr = (sbits << 12) | scval; } m8260_cpm_setbrg(info->state->smc_scc_num, baud_rate); restore_flags(flags);}static void rs_8xx_put_char(struct tty_struct *tty, unsigned char ch){ ser_info_t *info = (ser_info_t *)tty->driver_data; volatile cbd_t *bdp; if (serial_paranoia_check(info, tty->device, "rs_put_char")) return; if (!tty) return; bdp = info->tx_cur; while (bdp->cbd_sc & BD_SC_READY); *((char *)__va(bdp->cbd_bufaddr)) = ch; bdp->cbd_datlen = 1; bdp->cbd_sc |= BD_SC_READY; /* Get next BD. */ if (bdp->cbd_sc & BD_SC_WRAP) bdp = info->tx_bd_base; else bdp++; info->tx_cur = (cbd_t *)bdp;}static int rs_8xx_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count){ int c, ret = 0; ser_info_t *info = (ser_info_t *)tty->driver_data; volatile cbd_t *bdp; if (serial_paranoia_check(info, tty->device, "rs_write")) return 0; if (!tty) return 0; bdp = info->tx_cur; while (1) { c = MIN(count, TX_BUF_SIZE); if (c <= 0) break; if (bdp->cbd_sc & BD_SC_READY) { info->flags |= TX_WAKEUP; break; } if (from_user) { if (copy_from_user(__va(bdp->cbd_bufaddr), buf, c)) { if (!ret) ret = -EFAULT; break; } } else { memcpy(__va(bdp->cbd_bufaddr), buf, c); } bdp->cbd_datlen = c; bdp->cbd_sc |= BD_SC_READY; buf += c; count -= c; ret += c; /* Get next BD. */ if (bdp->cbd_sc & BD_SC_WRAP) bdp = info->tx_bd_base; else bdp++; info->tx_cur = (cbd_t *)bdp; } return ret;}static int rs_8xx_write_room(struct tty_struct *tty){ ser_info_t *info = (ser_info_t *)tty->driver_data; int ret; if (serial_paranoia_check(info, tty->device, "rs_write_room"))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -