📄 uart.c
字号:
*/ 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++; idx = PORT_NUM(state->smc_scc_num); if (PORT_IS_SCC(state->smc_scc_num)) { sccp = &cpmp->cp_scc[idx]; new_mode = (sbits << 12) | scval; prev_mode = sccp->scc_pmsr; if (!(prev_mode & SCU_PMSR_PEN)) /* If parity is disabled, mask out even/odd */ prev_mode &= ~(SCU_PMSR_TPM|SCU_PMSR_RPM); if (prev_mode != new_mode) sccp->scc_pmsr = new_mode; } else { smcp = &cpmp->cp_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 & (SMCMR_REN | SMCMR_TEN); new_mode = smcr_mk_clen(bits) | cval | SMCMR_SM_UART | prev_mode; if (!(prev_mode & SMCMR_PEN)) /* If parity is disabled, mask out even/odd */ prev_mode &= ~SMCMR_PM_EVEN; if (prev_mode != new_mode) smcp->smc_smcmr = new_mode; } m8xx_cpm_setbrg(PORT_BRG(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; unsigned char *cp; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_put_char")) return; if (!tty) return; local_irq_save(flags); bdp = info->tx_cur; /* Get next BD. */ if (bdp->cbd_sc & BD_SC_WRAP) info->tx_cur = info->tx_bd_base; else info->tx_cur = (cbd_t *)bdp + 1; local_irq_restore(flags); while (bdp->cbd_sc & BD_SC_READY); cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); *cp = ch; bdp->cbd_datlen = 1; bdp->cbd_sc |= BD_SC_READY;}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; unsigned char *cp; unsigned long flags;#ifdef CONFIG_KGDB_CONSOLE /* Try to let stub handle output. Returns true if it did. */ if (kgdb_output_string(buf, count)) return ret;#endif if (serial_paranoia_check(info, tty->device, "rs_write")) return 0; if (!tty) return 0; while (1) { c = MIN(count, TX_BUF_SIZE); if (c <= 0) break; local_irq_save(flags); bdp = info->tx_cur; if (bdp->cbd_sc & BD_SC_READY) { info->flags |= TX_WAKEUP; local_irq_restore(flags); break; } /* Get next BD. */ if (bdp->cbd_sc & BD_SC_WRAP) info->tx_cur = info->tx_bd_base; else info->tx_cur = (cbd_t *)bdp + 1; local_irq_restore(flags); cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); if (from_user) c -= copy_from_user((void *)cp, buf, c); else memcpy((void *)cp, buf, c); if (c) { bdp->cbd_datlen = c; bdp->cbd_sc |= BD_SC_READY; } else { /* Need to TX at least 1 char to keep CPM sane */ bdp->cbd_datlen = 1; *cp = 0; bdp->cbd_sc |= BD_SC_READY; if (!ret) ret = -EFAULT; break; } buf += c; count -= c; ret += c; } 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")) return 0; if ((info->tx_cur->cbd_sc & BD_SC_READY) == 0) { info->flags &= ~TX_WAKEUP; ret = TX_BUF_SIZE; } else { info->flags |= TX_WAKEUP; ret = 0; } return ret;}/* I could track this with transmit counters....maybe later.*/static int rs_8xx_chars_in_buffer(struct tty_struct *tty){ ser_info_t *info = (ser_info_t *)tty->driver_data; if (serial_paranoia_check(info, tty->device, "rs_chars_in_buffer")) return 0; return 0;}static void rs_8xx_flush_buffer(struct tty_struct *tty){ ser_info_t *info = (ser_info_t *)tty->driver_data; if (serial_paranoia_check(info, tty->device, "rs_flush_buffer")) return; /* There is nothing to "flush", whatever we gave the CPM * is on its way out. */ tty_wakeup(tty); info->flags &= ~TX_WAKEUP;}/* * This function is used to send a high-priority XON/XOFF character to * the device */static void rs_8xx_send_xchar(struct tty_struct *tty, char ch){ volatile cbd_t *bdp; unsigned char *cp; unsigned long flags; ser_info_t *info = (ser_info_t *)tty->driver_data; if (serial_paranoia_check(info, tty->device, "rs_send_char")) return; local_irq_save(flags); bdp = info->tx_cur; /* Get next BD. */ if (bdp->cbd_sc & BD_SC_WRAP) info->tx_cur = info->tx_bd_base; else info->tx_cur = (cbd_t *)bdp + 1; local_irq_restore(flags); while (bdp->cbd_sc & BD_SC_READY); cp = info->tx_va_base + ((bdp - info->tx_bd_base) * TX_BUF_SIZE); *cp = ch; bdp->cbd_datlen = 1; bdp->cbd_sc |= BD_SC_READY;}/* * ------------------------------------------------------------ * rs_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------ */static void rs_8xx_throttle(struct tty_struct * tty){ ser_info_t *info = (ser_info_t *)tty->driver_data;#ifdef SERIAL_DEBUG_THROTTLE char buf[64]; printk("throttle %s: %d....\n", _tty_name(tty, buf), tty->ldisc.chars_in_buffer(tty));#endif if (serial_paranoia_check(info, tty->device, "rs_throttle")) return; if (I_IXOFF(tty)) rs_8xx_send_xchar(tty, STOP_CHAR(tty));#ifdef modem_control if (tty->termios->c_cflag & CRTSCTS) info->MCR &= ~UART_MCR_RTS; cli(); serial_out(info, UART_MCR, info->MCR); sti();#endif}static void rs_8xx_unthrottle(struct tty_struct * tty){ ser_info_t *info = (ser_info_t *)tty->driver_data;#ifdef SERIAL_DEBUG_THROTTLE char buf[64]; printk("unthrottle %s: %d....\n", _tty_name(tty, buf), tty->ldisc.chars_in_buffer(tty));#endif if (serial_paranoia_check(info, tty->device, "rs_unthrottle")) return; if (I_IXOFF(tty)) { if (info->x_char) info->x_char = 0; else rs_8xx_send_xchar(tty, START_CHAR(tty)); }#ifdef modem_control if (tty->termios->c_cflag & CRTSCTS) info->MCR |= UART_MCR_RTS; cli(); serial_out(info, UART_MCR, info->MCR); sti();#endif}/* * ------------------------------------------------------------ * rs_ioctl() and friends * ------------------------------------------------------------ */#ifdef maybe/* * get_lsr_info - get line status register info * * Purpose: Let user call ioctl() to get info when the UART physically * is emptied. On bus types like RS485, the transmitter must * release the bus after transmitting. This must be done when * the transmit shift register is empty, not be done when the * transmit holding register is empty. This functionality * allows an RS485 driver to be written in user space. */static int get_lsr_info(struct async_struct * info, unsigned int *value){ unsigned char status; unsigned int result; cli(); status = serial_in(info, UART_LSR); sti(); result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); return put_user(result,value);}#endifstatic int get_modem_info(ser_info_t *info, unsigned int *value){ unsigned int result = 0;#ifdef modem_control unsigned char control, status; control = info->MCR; cli(); status = serial_in(info, UART_MSR); sti(); result = ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)#ifdef TIOCM_OUT1 | ((control & UART_MCR_OUT1) ? TIOCM_OUT1 : 0) | ((control & UART_MCR_OUT2) ? TIOCM_OUT2 : 0)#endif | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);#endif return put_user(result,value);}static int set_modem_info(ser_info_t *info, unsigned int cmd, unsigned int *value){ int error; unsigned int arg; error = get_user(arg, value); if (error) return error;#ifdef modem_control switch (cmd) { case TIOCMBIS: if (arg & TIOCM_RTS) info->MCR |= UART_MCR_RTS; if (arg & TIOCM_DTR) info->MCR |= UART_MCR_DTR;#ifdef TIOCM_OUT1 if (arg & TIOCM_OUT1) info->MCR |= UART_MCR_OUT1; if (arg & TIOCM_OUT2) info->MCR |= UART_MCR_OUT2;#endif break; case TIOCMBIC: if (arg & TIOCM_RTS) info->MCR &= ~UART_MCR_RTS; if (arg & TIOCM_DTR) info->MCR &= ~UART_MCR_DTR;#ifdef TIOCM_OUT1 if (arg & TIOCM_OUT1) info->MCR &= ~UART_MCR_OUT1; if (arg & TIOCM_OUT2) info->MCR &= ~UART_MCR_OUT2;#endif break; case TIOCMSET: info->MCR = ((info->MCR & ~(UART_MCR_RTS |#ifdef TIOCM_OUT1 UART_MCR_OUT1 | UART_MCR_OUT2 |#endif UART_MCR_DTR)) | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)#ifdef TIOCM_OUT1 | ((arg & TIOCM_OUT1) ? UART_MCR_OUT1 : 0) | ((arg & TIOCM_OUT2) ? UART_MCR_OUT2 : 0)#endif | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0)); break; default: return -EINVAL; } cli(); serial_out(info, UART_MCR, info->MCR); sti();#endif return 0;}/* Sending a break is a two step process on the SMC/SCC. It is accomplished * by sending a STOP TRANSMIT command followed by a RESTART TRANSMIT * command. We take advantage of the begin/end functions to make this * happen. */static ushort smc_chan_map[] = { CPM_CR_CH_SMC1, CPM_CR_CH_SMC2};static ushort scc_chan_map[] = { CPM_CR_CH_SCC1, CPM_CR_CH_SCC2, CPM_CR_CH_SCC3, CPM_CR_CH_SCC4};static void begin_break(ser_info_t *info){ volatile cpm8xx_t *cp; ushort chan; int idx; cp = cpmp; idx = PORT_NUM(info->state->smc_scc_num); if (PORT_IS_SCC(info->state->smc_scc_num)) chan = scc_chan_map[idx]; else chan = smc_chan_map[idx]; cp->cp_cpcr = mk_cr_cmd(chan, CPM_CR_STOP_TX) | CPM_CR_FLG; while (cp->cp_cpcr & CPM_CR_FLG);}static void end_break(ser_info_t *info){ volatile cpm8xx_t *cp; ushort chan; int idx; cp = cpmp; idx = PORT_NUM(info->state->smc_scc_num); if (PORT_IS_SCC(info->state->smc_scc_num)) chan = scc_chan_map[idx]; else chan = smc_chan_map[idx]; cp->cp_cpcr = mk_cr_cmd(chan, CPM_CR_RESTART_TX) | CPM_CR_FLG; while (cp->cp_cpcr & CPM_CR_FLG);}/* * This routine sends a break character out the serial port. */static void send_break(ser_info_t *info, int duration){ current->state = TASK_INTERRUPTIBLE;#ifdef SERIAL_DEBUG_SEND_BREAK printk("rs_send_break(%d) jiff=%lu...", duration, jiffies);#endif begin_break(info); schedule_timeout(duration); end_break(info);#ifdef SERIAL_DEBUG_SEND_BREAK printk("done jiffies=%lu\n", jiffies);#endif}static int rs_8xx_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg){ int error; ser_info_t *info = (ser_info_t *)tty->driver_data; int retval; struct async_icount cnow; /* kernel counter temps */ struct serial_icounter_struct *p_cuser; /* user space */ if (serial_paranoia_check(info, tty->device, "rs_ioctl")) return -ENODEV; if ((cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; } switch (cmd) { case TCSBRK: /* SVID version: non-zero arg --> no break */ retval = tty_check_change(tty); if (retval) return retval; tty_wait_until_sent(tty, 0); if (signal_pending(current)) return -EINTR; if (!arg) { send_break(info, HZ/4); /* 1/4 second */ if (signal_pending(current)) return -EINTR; } return 0; case TCSBRKP: /* support for POSIX tcsendbreak() */ retval = tty_check_change(tty); if (retval) return retval; tty_wait_until_sent(tty, 0); if (signal_pending(current)) return -EINTR; send_break(info, arg ? arg*(HZ/10) : HZ/4); if (signal_pending(current)) return -EINTR; return 0; case TIOCSBRK: retval = tty_check_change(tty); if (retval) return retval; tty_wait_until_sent(tty, 0); begin_break(info);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -