📄 serial.c
字号:
2. B B E or F__________________..__ V.._|__________|__________|______ | |valid data "valid" or parity errorMultiple frame errors with data == 0x00 (B),but the part of the break trigs is interpreted as a start bit (and possiblysome 0 bits followed by a number of 1 bits and a stop bit).Depending on parity settings etc. this last character can be eithera fake "valid" char (F) or have a parity error (E).If the character is valid it will be put in the buffer,we set info->errorcode = ERRCODE_SET_BREAK so the receive interruptwill set the flags so the tty will handle it,if it's an error byte it will not be put in the bufferand we set info->errorcode = ERRCODE_INSERT_BREAK.To distinguish a V byte in 1. from an F byte in 2. we keep a timestampof the last faulty char (B) and compares it with the current time:If the time elapsed time is less then 2*char_time_usec we will assumeit's a faked F char and not a Valid char and set info->errorcode = ERRCODE_SET_BREAK. Flaws in the above solution:~~~~~~~~~~~~~~~~~~~~~~~~~~~~We use the timer to distinguish a F character from a V character,if a V character is to close after the break we might make the wrong decision.TODO: The break will be delayed until an F or V character is received.*/static void _INLINE_ handle_ser_interrupt(struct e100_serial *info){ unsigned char rstat = info->port[REG_STATUS];#ifdef SERIAL_DEBUG_INTR printk("Interrupt from serport %d\n", i);#endif/* DEBUG_LOG(info->line, "ser_interrupt stat %03X\n", rstat | (i << 8)); */ if (rstat & SER_ERROR_MASK) { unsigned char data; info->last_rx_active_usec = GET_JIFFIES_USEC(); info->last_rx_active = jiffies; /* If we got an error, we must reset it by reading the * data_in field */ data = info->port[REG_DATA]; if (!data && (rstat & SER_FRAMING_ERR_MASK)) { /* Most likely a break, but we get interrupts over and * over again. */ if (!info->break_detected_cnt) { DEBUG_LOG(info->line, "#BRK start\n", 0); } if (rstat & SER_RXD_MASK) { /* The RX pin is high now, so the break * must be over, but.... * we can't really know if we will get another * last byte ending the break or not. * And we don't know if the byte (if any) will * have an error or look valid. */ DEBUG_LOG(info->line, "# BL BRK\n", 0); info->errorcode = ERRCODE_INSERT_BREAK; } info->break_detected_cnt++; } else { /* The error does not look like a break, but could be * the end of one */ if (info->break_detected_cnt) { DEBUG_LOG(info->line, "EBRK %i\n", info->break_detected_cnt); info->errorcode = ERRCODE_INSERT_BREAK; } else { if (info->errorcode == ERRCODE_INSERT_BREAK) add_char_and_flag(info, '\0', TTY_BREAK); if (rstat & SER_PAR_ERR_MASK) add_char_and_flag(info, data, TTY_PARITY); else if (rstat & SER_OVERRUN_MASK) add_char_and_flag(info, data, TTY_OVERRUN); else if (rstat & SER_FRAMING_ERR_MASK) add_char_and_flag(info, data, TTY_FRAME); info->errorcode = 0; } info->break_detected_cnt = 0; DEBUG_LOG(info->line, "#iERR s d %04X\n", ((rstat & SER_ERROR_MASK) << 8) | data); } PROCSTAT(ser_stat[info->line].early_errors_cnt++); } else { /* It was a valid byte, now let the DMA do the rest */ unsigned long curr_time_u = GET_JIFFIES_USEC(); unsigned long curr_time = jiffies; if (info->break_detected_cnt) { /* Detect if this character is a new valid char or the * last char in a break sequence: If LSBits are 0 and * MSBits are high AND the time is close to the * previous interrupt we should discard it. */ long elapsed_usec = (curr_time - info->last_rx_active) * (1000000/HZ) + curr_time_u - info->last_rx_active_usec; if (elapsed_usec < 2*info->char_time_usec) { DEBUG_LOG(info->line, "FBRK %i\n", info->line); /* Report as BREAK (error) and let * receive_chars() handle it */ info->errorcode = ERRCODE_SET_BREAK; } else { DEBUG_LOG(info->line, "Not end of BRK (V)%i\n", info->line); } DEBUG_LOG(info->line, "num brk %i\n", info->break_detected_cnt); }#ifdef SERIAL_DEBUG_INTR printk("** OK, disabling ser_interupts\n");#endif e100_disable_serial_data_irq(info); info->break_detected_cnt = 0; PROCSTAT(ser_stat[info->line].ser_ints_ok_cnt++); DEBUG_LOG(info->line, "ser_int OK %d\n", info->line); } /* Restarting the DMA never hurts */ *info->icmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, restart); START_FLUSH_FAST_TIMER(info, "ser_int");} /* handle_ser_interrupt */static void ser_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct e100_serial *info; int i; for (i = 0; i < NR_PORTS; i++) { info = rs_table + i; if (!info->uses_dma) continue; /* Which line caused the irq? */ if (*R_IRQ_MASK1_RD & (1U << (8+2*info->line))) { handle_ser_interrupt(info); } }} /* ser_interrupt */#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_){ struct e100_serial *info = (struct e100_serial *) 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 e100_serial *info = (struct e100_serial *) private_; struct tty_struct *tty; tty = info->tty; if (!tty) return; tty_hangup(tty);}static int startup(struct e100_serial * info){ unsigned long flags; unsigned long xmit_page; unsigned char *recv_page; xmit_page = get_zeroed_page(GFP_KERNEL); if (!xmit_page) return -ENOMEM; recv_page = kmalloc(2 * SERIAL_RECV_SIZE + SERIAL_RECV_DESCRIPTORS * SERIAL_DESCR_BUF_SIZE, GFP_KERNEL); if (!recv_page) { free_page(xmit_page); return -ENOMEM; } save_flags(flags); cli(); /* if it was already initialized, skip this */ if (info->flags & ASYNC_INITIALIZED) { restore_flags(flags); free_page(xmit_page); kfree(recv_page); return 0; } if (info->xmit.buf) free_page(xmit_page); else info->xmit.buf = (unsigned char *) xmit_page; if (info->recv.buf) kfree(recv_page); else { info->recv.buf = (unsigned char *) recv_page; info->flag_buf = info->recv.buf + SERIAL_RECV_SIZE; }#ifdef SERIAL_DEBUG_OPEN printk("starting up ttyS%d (xmit_buf 0x%p, recv_buf 0x%p)...\n", info->line, info->xmit.buf, info->recv.buf);#endif#ifdef CONFIG_SVINTO_SIM /* Bits and pieces collected from below. Better to have them in one ifdef:ed clause than to mix in a lot of ifdefs, right? */ if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); info->xmit.head = info->xmit.tail = 0; info->recv.head = info->recv.tail = 0; /* No real action in the simulator, but may set info important to ioctl. */ change_speed(info);#else /* * Clear the FIFO buffers and disable them * (they will be reenabled in change_speed()) */ /* * Reset the DMA channels and make sure their interrupts are cleared */ info->uses_dma = 1; *info->icmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, reset); *info->ocmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, reset); /* Wait until reset cycle is complete */ while (IO_EXTRACT(R_DMA_CH6_CMD, cmd, *info->icmdadr) == IO_STATE_VALUE(R_DMA_CH6_CMD, cmd, reset)); while (IO_EXTRACT(R_DMA_CH6_CMD, cmd, *info->ocmdadr) == IO_STATE_VALUE(R_DMA_CH6_CMD, cmd, reset)); /* Make sure the irqs are cleared */ *info->iclrintradr = IO_STATE(R_DMA_CH6_CLR_INTR, clr_descr, do) | IO_STATE(R_DMA_CH6_CLR_INTR, clr_eop, do); *info->oclrintradr = IO_STATE(R_DMA_CH6_CLR_INTR, clr_descr, do) | IO_STATE(R_DMA_CH6_CLR_INTR, clr_eop, do); if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); info->xmit.head = info->xmit.tail = 0; info->recv.head = info->recv.tail = 0; /* * and set the speed and other flags of the serial port * this will start the rx/tx as well */#ifdef SERIAL_HANDLE_EARLY_ERRORS e100_enable_serial_data_irq(info);#endif change_speed(info); /* dummy read to reset any serial errors */ (void)info->port[REG_DATA]; /* enable the interrupts */ e100_enable_txdma_irq(info); e100_enable_rxdma_irq(info); info->tr_running = 0; /* to be sure we don't lock up the transmitter */ /* setup the dma input descriptor and start dma */ start_receive(info); /* for safety, make sure the descriptors last result is 0 bytes written */ info->tr_descr.sw_len = 0; info->tr_descr.hw_len = 0; info->tr_descr.status = 0; /* enable RTS/DTR last */ e100_rts(info, 1); e100_dtr(info, 1); #endif /* CONFIG_SVINTO_SIM */ info->flags |= ASYNC_INITIALIZED; restore_flags(flags); return 0;}/* * 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(struct e100_serial * info){ unsigned long flags;#ifndef CONFIG_SVINTO_SIM /* shut down the transmitter and receiver */ e100_disable_rx(info); info->port[REG_TR_CTRL] = (info->tx_ctrl &= ~0x40); e100_disable_rxdma_irq(info); e100_disable_txdma_irq(info); info->tr_running = 0; /* reset both dma channels */ *info->icmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, reset); *info->ocmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, reset); info->uses_dma = 0;#endif /* CONFIG_SVINTO_SIM */ if (!(info->flags & ASYNC_INITIALIZED)) return; #ifdef SERIAL_DEBUG_OPEN printk("Shutting down serial port %d (irq %d)....\n", info->line, info->irq);#endif save_flags(flags); cli(); /* Disable interrupts */ if (info->xmit.buf) { free_page((unsigned long)info->xmit.buf); info->xmit.buf = NULL; } if (info->recv.buf) { kfree(info->recv.buf); info->recv.buf = NULL; info->flag_buf = NULL; } if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { /* hang up DTR and RTS if HUPCL is enabled */ e100_dtr(info, 0); e100_rts(info, 0); /* could check CRTSCTS before doing this */ } if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); info->flags &= ~ASYNC_INITIALIZED; restore_flags(flags);}/* change baud rate and other assorted parameters */static void change_speed(struct e100_serial *info){ unsigned int cflag; /* first some safety checks */ if (!info->tty || !info->tty->termios) return; if (!info->port) return; cflag = info->tty->termios->c_cflag; /* possibly, the tx/rx should be disabled first to do this safely */ /* change baud-rate and write it to the hardware */ info->baud = cflag_to_baud(cflag); #ifndef CONFIG_SVINTO_SIM info->port[REG_BAUD] = cflag_to_etrax_baud(cflag); /* start with default settings and then fill in changes */ /* 8 bit, no/even parity */ info->rx_ctrl &= ~(IO_MASK(R_SERIAL0_REC_CTRL, rec_bitnr) | IO_MASK(R_SERIAL0_REC_CTRL, rec_par_en) | IO_MASK(R_SERIAL0_R
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -