/***************************************************************************** ; ; (C) Unpublished Work of ADMtek Incorporated. All Rights Reserved. ; ; THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL, ; PROPRIETARY AND TRADESECRET INFORMATION OF ADMTEK INCORPORATED. ; ACCESS TO THIS WORK IS RESTRICTED TO (I) ADMTEK EMPLOYEES WHO HAVE A ; NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF THEIR ASSIGNMENTS ; AND (II) ENTITIES OTHER THAN ADMTEK WHO HAVE ENTERED INTO APPROPRIATE ; LICENSE AGREEMENTS. NO PART OF THIS WORK MAY BE USED, PRACTICED, ; PERFORMED, COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ; ABBRIDGED, CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, ; TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF ADMTEK. ; ANY USE OR EXPLOITATION OF THIS WORK WITHOUT AUTHORIZATION COULD ; SUBJECT THE PERPERTRATOR TO CRIMINAL AND CIVIL LIABILITY. ; ;------------------------------------------------------------------------------ ; ; Project : ADM5120 ; Creator : daniell@admtek.com.tw ; File : arch/mips/am5120/printf.c ; Date : 2003.3.4 ; Abstract: ; ;Modification History: ; ;*****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define SERIAL_DEBUG_OPEN 1 //#define SERIAL_DEBUG_INTR 1 /* * IN_W */ static inline unsigned long IN_W(unsigned long _addr) { return (*(volatile unsigned long *)(_addr)); } /* * OUT_W */ static inline void OUT_W(unsigned long _addr, unsigned long _value) { (*((volatile unsigned long *)(_addr))) = _value; } /* * serial_in */ static unsigned int serial_in(struct async_struct *info, int offset) { return IN_W(info->port + offset); } /* * serial_out */ static void serial_out(struct async_struct *info, int offset, int value) { OUT_W(info->port + offset, value); } /* * serial state */ static struct serial_state rs_table[] = { {baud_base:UART_9600bps_DIVISOR, port:KSEG1ADDR(UART0_BASE), irq:1, flags:STD_COM_FLAGS, type:SERIAL_IO_MEM}, {baud_base:UART_9600bps_DIVISOR, port:KSEG1ADDR(UART1_BASE), irq:2, flags:STD_COM_FLAGS, type:SERIAL_IO_MEM} }; /*------------------------------------------------------- * prom_printf * *-----------------------------------------------------*/ /* * Hooks to fake "prom" console I/O before devices * are fully initialized. */ static struct async_struct prom_port_info = {0}; void __init setup_prom_printf(int tty_no) { struct serial_state *ser = &rs_table[tty_no]; //printk("setup_prom_printf: tty_no: %d\n", tty_no); prom_port_info.state = ser; prom_port_info.magic = SERIAL_MAGIC; prom_port_info.port = ser->port; prom_port_info.flags = ser->flags; /* set baudrate */ //printk("setting baudrate of ttyS%d: %d", tty_no, prom_port_info.state->baud_base); serial_out(&prom_port_info, UART_LCR_L_REG, prom_port_info.state->baud_base); serial_out(&prom_port_info, UART_LCR_M_REG, prom_port_info.state->baud_base >> 8); /* Set default line mode */ serial_out(&prom_port_info, UART_LCR_H_REG, UART_WLEN_8BITS | UART_ENABLE_FIFO); /* Enable uart port */ serial_out(&prom_port_info, UART_CR_REG, UART_PORT_EN); } /* * putPromChar */ int putPromChar(char c) { if (!prom_port_info.state) { /* need to init device first */ return 0; } while ((serial_in(&prom_port_info, UART_FR_REG) & UART_TX_FIFO_FULL) != 0) ; serial_out(&prom_port_info, UART_DR_REG, c); return 1; } /* * getPromChar */ char getPromChar(void) { if (!prom_port_info.state) /* need to init device first */ return 0; if ((serial_in(&prom_port_info, UART_FR_REG) & UART_RX_FIFO_EMPTY)) return 0; return(serial_in(&prom_port_info, UART_DR_REG)); } static char buf[1024]; /* * prom_printf */ void __init prom_printf(char *fmt, ...) { va_list args; int l; char *p, *buf_end; long flags; int putPromChar(char); /* Low level, brute force, not SMP safe... */ save_and_cli(flags); va_start(args, fmt); l = vsprintf(buf, fmt, args); /* hopefully i < sizeof(buf) */ va_end(args); buf_end = buf + l; for (p = buf; p < buf_end; p++) { /* Crude cr/nl handling is better than none */ if(*p == '\n')putPromChar('\r'); putPromChar(*p); } restore_flags(flags); } /*------------------------------------------------------- * console driver * *-----------------------------------------------------*/ /* * Print a string to the serial port trying not to disturb * any possible real use of the port... * * The console_lock must be held when we get here. */ static void serial_console_write(struct console *co, const char *s, unsigned count) { int i; for (i = 0; i < count; i++, s++) { if (*s == '\n') putPromChar('\r'); putPromChar(*s); } } /* * Receive character from the serial port */ static int serial_console_wait_key(struct console *co) { return 0; } /* * Get serial console device */ static kdev_t serial_console_device(struct console *c) { return MKDEV(TTY_MAJOR, 64 + c->index); } /* * Setup initial baud/bits/parity. We do two things here: * - construct a cflag setting for the first rs_open() * - initialize the serial port * Return non-zero if we didn't find a serial port. */ static int __init serial_console_setup(struct console *co, char *options) { return 0; } /* * Console data structure */ static struct console sercons = { name: "ttyS", write: serial_console_write, device: serial_console_device, // wait_key: serial_console_wait_key, setup: serial_console_setup, flags: CON_PRINTBUFFER, index: -1, }; /* * Register console. */ void __init am5120_serial_console_init(void) { register_console(&sercons); } /*------------------------------------------------------ * Register tty driver * *----------------------------------------------------*/ static char *serial_version = "0.02"; static char *serial_revdate = "2005-12-06 ;)"; #define LOCAL_VERSTRING "" #define NR_PORTS (sizeof(rs_table)/sizeof(struct serial_state)) #define WAKEUP_CHARS 256 #define RS_ISR_PASS_LIMIT 256 static unsigned char *tmp_buf; #ifdef DECLARE_MUTEX static DECLARE_MUTEX(tmp_buf_sem); #else static struct semaphore tmp_buf_sem = MUTEX; #endif static struct tty_driver dev_tty_driver; //static struct tty_driver dev_tty2_driver; static int serial_refcount; static struct tty_struct *serial_table[NR_PORTS]; static struct termios *serial_termios[NR_PORTS]; static struct termios *serial_termios_locked[NR_PORTS]; static unsigned char *tmp_buf = 0; static struct async_struct *IRQ_ports[NR_IRQS] = {0,0}; static void do_softint(void *private_) { struct async_struct *info = (struct async_struct *) 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); #ifdef SERIAL_HAVE_POLL_WAIT wake_up_interruptible(&tty->poll_wait); #endif } } static int get_async_struct(int line, struct async_struct **ret_info) { struct async_struct *info; struct serial_state *sstate; sstate = rs_table + line; sstate->count++; if (sstate->info) { *ret_info = sstate->info; return 0; } info = kmalloc(sizeof(struct async_struct), GFP_KERNEL); if (!info) { sstate->count--; return -ENOMEM; } memset(info, 0, sizeof(struct async_struct)); init_waitqueue_head(&info->open_wait); init_waitqueue_head(&info->close_wait); init_waitqueue_head(&info->delta_msr_wait); info->magic = SERIAL_MAGIC; info->port = sstate->port; info->flags = sstate->flags; info->io_type = sstate->io_type; info->iomem_base = sstate->iomem_base; info->iomem_reg_shift = sstate->iomem_reg_shift; info->xmit_fifo_size = sstate->xmit_fifo_size; info->line = line; info->tqueue.routine = do_softint; info->tqueue.data = info; info->state = sstate; if (sstate->info) { kfree(info); *ret_info = sstate->info; return 0; } *ret_info = sstate->info = info; return 0; } static void transmit_chars(struct async_struct *info, int *intr_done) { if (info->x_char) { serial_out(info, UART_DR_REG, info->x_char); info->state->icount.tx++; info->x_char = 0; if (intr_done) *intr_done = 0; return; } if (info->xmit.head == info->xmit.tail || info->tty->stopped || info->tty->hw_stopped) { info->IER &= ~UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); return; } do { while ((serial_in(info, UART_FR_REG) & UART_TX_FIFO_FULL) != 0) ; serial_out(info, UART_DR_REG, info->xmit.buf[info->xmit.tail]); info->xmit.tail = (info->xmit.tail + 1) & (SERIAL_XMIT_SIZE-1); info->state->icount.tx++; if (info->xmit.head == info->xmit.tail) break; } while (1); //if (CIRC_CNT(info->xmit.head, // info->xmit.tail, // SERIAL_XMIT_SIZE) < WAKEUP_CHARS) //rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); #ifdef SERIAL_DEBUG_INTR printk("THRE..."); #endif if (intr_done) *intr_done = 0; if (info->xmit.head == info->xmit.tail) { info->IER &= ~UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); } } static void receive_chars(struct async_struct *info, struct pt_regs * regs) { struct tty_struct *tty = info->tty; unsigned char ch; //int ignored = 0; struct async_icount *icount; unsigned int status; unsigned int rsr_flag; icount = &info->state->icount; do { ch = serial_in(info, UART_DR_REG); rsr_flag = serial_in(info, UART_RSR_REG); serial_out(info, UART_RSR_REG, rsr_flag); if (rsr_flag & UART_RX_ERROR) goto ignore_char; if (tty->flip.count >= TTY_FLIPBUF_SIZE) goto ignore_char; *tty->flip.char_buf_ptr = ch; icount->rx++; #ifdef SERIAL_DEBUG_INTR printk("DR%02x...", ch); #endif *tty->flip.flag_buf_ptr = 0; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; status = serial_in(info, UART_FR_REG); } while (!(status & UART_RX_FIFO_EMPTY)); ignore_char: tty_flip_buffer_push(tty); } /* * This is the serial driver's interrupt routine for a single port */ static void rs_interrupt(int irq, void *dev_id, struct pt_regs * regs) { int status; int pass_counter = 0; struct async_struct * info; #ifdef SERIAL_DEBUG_INTR printk("rs_interrupt(%d)...", irq); #endif info = IRQ_ports[irq]; if (!info || !info->tty) return; do { status = serial_in(info, UART_IIR_REG); if (status & (UART_RX_INT | UART_RX_TIMEOUT_INT)) { //if ((serial_in(info, UART_FR_REG) & UART_RX_FIFO_EMPTY)) // break; receive_chars(info, regs); } if (status & UART_TX_INT) { transmit_chars(info, 0); } if (pass_counter++ > RS_ISR_PASS_LIMIT) { break; } } while (serial_in(info, UART_IIR_REG) & (UART_RX_INT | UART_RX_TIMEOUT_INT | UART_TX_INT)); info->last_active = jiffies; } static int startup(struct async_struct * info) { unsigned long flags; int retval=0; void (*handler)(int, void *, struct pt_regs *); struct serial_state *state= info->state; unsigned long page; if (info->flags & ASYNC_INITIALIZED) return retval; page = get_zeroed_page(GFP_KERNEL); if (!page) return -ENOMEM; save_flags(flags); cli(); if (!CONFIGURED_SERIAL_PORT(state) || !state->type) { if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); free_page(page); goto errout; } if (info->xmit.buf) free_page(page); else info->xmit.buf = (unsigned char *) page; #ifdef SERIAL_DEBUG_OPEN printk("starting up ttyS%d (irq %d)...", info->line, state->irq); #endif /* * Allocate the IRQ if necessary */ if ((!IRQ_ports[state->irq] || !IRQ_ports[state->irq]->next_port)) { if (IRQ_ports[state->irq]) { retval = -EBUSY; goto errout; } else handler = rs_interrupt; retval = request_irq(state->irq, handler, SA_SHIRQ, "serial", &IRQ_ports[state->irq]); if (retval) { if (capable(CAP_SYS_ADMIN)) { if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); } goto errout; } } /* * Insert serial port into IRQ chain. */ info->prev_port = 0; info->next_port = IRQ_ports[state->irq]; if (info->next_port) info->next_port->prev_port = info; IRQ_ports[state->irq] = info; /* * Now, initialize the UART */ //printk("setting baudrate of ttyS%d: %d", info->line, state->baud_base); serial_out(info, UART_LCR_L_REG, state->baud_base); serial_out(info, UART_LCR_M_REG, state->baud_base >> 8); serial_out(info, UART_LCR_H_REG, (UART_WLEN_8BITS | UART_ENABLE_FIFO)); /* * Finally, enable interrupts */ info->IER = UART_RX_INT_EN | UART_RX_TIMEOUT_INT_EN | UART_PORT_EN; serial_out(info, UART_CR_REG, info->IER); if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); info->xmit.head = info->xmit.tail = 0; /* * and set the speed of the serial port */ info->flags |= ASYNC_INITIALIZED; restore_flags(flags); return 0; errout: restore_flags(flags); return retval; } /* * This routine is called whenever a serial port is opened. It * enables interrupts for a serial port, linking in its async structure into * the IRQ chain. It also performs the serial-specific * initialization for the tty structure. */ static int rs_open(struct tty_struct *tty, struct file * filp) { struct async_struct *info; int retval, line; unsigned long page; line = MINOR(tty->device) - tty->driver.minor_start; //printk("rs_open 1 %s%d, \n", tty->driver.name, line); if ((line < 0) || (line >= NR_PORTS)) { return -ENODEV; } retval = get_async_struct(line, &info); if (retval) { return retval; } tty->driver_data = info; info->tty = tty; #ifdef SERIAL_DEBUG_OPEN printk("rs_open %s%d, count = %d\n", tty->driver.name, info->line, info->state->count); #endif info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; if (!tmp_buf) { page = get_zeroed_page(GFP_KERNEL); if (!page) { return -ENOMEM; } if (tmp_buf) free_page(page); else tmp_buf = (unsigned char *) page; } /* * If the port is the middle of closing, bail out now */ if (tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING)) { if (info->flags & ASYNC_CLOSING) interruptible_sleep_on(&info->close_wait); return -EAGAIN; } /* * Start up serial port */ retval = startup(info); if (retval) { return retval; } #if 0 retval = block_til_ready(tty, filp, info); if (retval) { #ifdef SERIAL_DEBUG_OPEN printk("rs_open returning after block_til_ready with %d\n", retval); #endif return retval; } #endif if ((info->state->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) { if (tty->driver.subtype == SERIAL_TYPE_NORMAL) *tty->termios = info->state->normal_termios; else *tty->termios = info->state->callout_termios; } info->session = current->session; info->pgrp = current->pgrp; #ifdef SERIAL_DEBUG_OPEN printk("rs_open ttyS%d successful...", info->line); #endif return 0; } /* * rs_close() * * This routine is called when the serial port gets closed. First, we * wait for the last remaining data to be sent. Then, we unlink its * async structure from the interrupt chain if necessary, and we free * that IRQ if nothing is left in the chain. */ static void rs_close(struct tty_struct *tty, struct file * filp) { struct async_struct * info = (struct async_struct *)tty->driver_data; struct serial_state *state; unsigned long flags; state = info->state; save_flags(flags); cli(); //if (tty_hung_up_p(filp)) { // DBG_CNT("before DEC-hung"); // restore_flags(flags); // return; //} #ifdef SERIAL_DEBUG_OPEN printk("rs_close ttyS%d, count = %d\n", info->line, state->count); #endif if ((tty->count == 1) && (state->count != 1)) { /* * Uh, oh. tty->count is 1, which means that the tty * structure will be freed. state->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("rs_close: bad serial port count; tty->count is 1, " // "state->count is %d\n", state->count); state->count = 1; } if (--state->count < 0) { //printk("rs_close: bad serial port count for ttys%d: %d\n", // info->line, state->count); state->count = 0; } restore_flags(flags); return; #if 0 info->flags |= ASYNC_CLOSING; restore_flags(flags); /* * Save the termios structure, since this port may have * separate termios for callout and dialin. */ if (info->flags & ASYNC_NORMAL_ACTIVE) info->state->normal_termios = *tty->termios; if (info->flags & ASYNC_CALLOUT_ACTIVE) info->state->callout_termios = *tty->termios; /* * Now we wait for the transmit buffer to clear; and we notify * the line discipline to only process XON/XOFF characters. */ tty->closing = 1; if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) tty_wait_until_sent(tty, info->closing_wait); /* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register. */ //info->IER &= ~UART_IER_RLSI; //info->read_status_mask &= ~UART_LSR_DR; //if (info->flags & ASYNC_INITIALIZED) { // serial_out(info, UART_IER, info->IER); /* * Before we drop DTR, make sure the UART transmitter * has completely drained; this is especially * important if there is a transmit FIFO! */ // rs_wait_until_sent(tty, info->timeout); //} //shutdown(info); if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); tty->closing = 0; info->event = 0; info->tty = 0; //if (info->blocked_open) { // if (info->close_delay) { // set_current_state(TASK_INTERRUPTIBLE); // schedule_timeout(info->close_delay); // } // wake_up_interruptible(&info->open_wait); //} info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE| ASYNC_CLOSING); wake_up_interruptible(&info->close_wait); #endif } static int rs_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count) { int c, ret = 0; struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; if (!tty || !info->xmit.buf || !tmp_buf) return 0; save_flags(flags); if (from_user) { down(&tmp_buf_sem); while (1) { int c1; c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); if (count < c) c = count; if (c <= 0) break; c -= copy_from_user(tmp_buf, buf, c); if (!c) { if (!ret) ret = -EFAULT; break; } cli(); c1 = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); if (c1 < c) c = c1; memcpy(info->xmit.buf + info->xmit.head, tmp_buf, c); info->xmit.head = ((info->xmit.head + c) & (SERIAL_XMIT_SIZE-1)); restore_flags(flags); buf += c; count -= c; ret += c; } up(&tmp_buf_sem); } else { cli(); while (1) { c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); if (count < c) c = count; if (c <= 0) { break; } memcpy(info->xmit.buf + info->xmit.head, buf, c); info->xmit.head = ((info->xmit.head + c) & (SERIAL_XMIT_SIZE-1)); buf += c; count -= c; ret += c; } restore_flags(flags); } if (info->xmit.head != info->xmit.tail && !tty->stopped && !tty->hw_stopped && !(info->IER & UART_TX_INT)) { info->IER |= UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); } return ret; } static void rs_put_char(struct tty_struct *tty, unsigned char ch) { struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; if (!tty || !info->xmit.buf) return; save_flags(flags); cli(); if (CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE) == 0) { restore_flags(flags); return; } info->xmit.buf[info->xmit.head] = ch; info->xmit.head = (info->xmit.head + 1) & (SERIAL_XMIT_SIZE-1); restore_flags(flags); } static void rs_flush_chars(struct tty_struct *tty) { struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; if (info->xmit.head == info->xmit.tail || tty->stopped || tty->hw_stopped || !info->xmit.buf) return; save_flags(flags); cli(); info->IER |= UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); restore_flags(flags); } static int rs_write_room(struct tty_struct *tty) { struct async_struct *info = (struct async_struct *)tty->driver_data; return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); } static int rs_chars_in_buffer(struct tty_struct *tty) { struct async_struct *info = (struct async_struct *)tty->driver_data; return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); } static void rs_flush_buffer(struct tty_struct *tty) { struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; save_flags(flags); cli(); info->xmit.head = info->xmit.tail = 0; restore_flags(flags); wake_up_interruptible(&tty->write_wait); #ifdef SERIAL_HAVE_POLL_WAIT wake_up_interruptible(&tty->poll_wait); #endif if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); } static int get_serial_info(struct async_struct * info, struct serial_struct * retinfo) { struct serial_struct tmp; struct serial_state *state = info->state; if (!retinfo) return -EFAULT; memset(&tmp, 0, sizeof(tmp)); tmp.type = state->type; tmp.line = state->line; tmp.port = state->port; //if (HIGH_BITS_OFFSET) // tmp.port_high = state->port >> HIGH_BITS_OFFSET; //else tmp.port_high = 0; tmp.irq = state->irq; tmp.flags = state->flags; tmp.xmit_fifo_size = state->xmit_fifo_size; tmp.baud_base = state->baud_base; tmp.close_delay = state->close_delay; tmp.closing_wait = state->closing_wait; tmp.custom_divisor = state->custom_divisor; tmp.hub6 = state->hub6; tmp.io_type = state->io_type; if (copy_to_user(retinfo,&tmp,sizeof(*retinfo))) return -EFAULT; return 0; } //#if 0 static int set_serial_info(struct async_struct * info, struct serial_struct * new_info) { struct serial_struct new_serial; struct serial_state old_state, *state; unsigned int i,change_irq,change_port; int retval = 0; unsigned long new_port; if (copy_from_user(&new_serial,new_info,sizeof(new_serial))) return -EFAULT; state = info->state; old_state = *state; new_port = new_serial.port; // if (HIGH_BITS_OFFSET) // new_port += (unsigned long) new_serial.port_high << HIGH_BITS_OFFSET; change_irq = new_serial.irq != state->irq; change_port = (new_port != ((int) state->port)) || (new_serial.hub6 != state->hub6); if (!capable(CAP_SYS_ADMIN)) { if (change_irq || change_port || (new_serial.baud_base != state->baud_base) || (new_serial.type != state->type) || (new_serial.close_delay != state->close_delay) || (new_serial.xmit_fifo_size != state->xmit_fifo_size) || ((new_serial.flags & ~ASYNC_USR_MASK) != (state->flags & ~ASYNC_USR_MASK))) return -EPERM; state->flags = ((state->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK)); info->flags = ((info->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK)); state->custom_divisor = new_serial.custom_divisor; goto check_and_exit; } new_serial.irq = irq_cannonicalize(new_serial.irq); if ((new_serial.irq >= NR_IRQS) || (new_serial.irq < 0) || (new_serial.baud_base < 1200)|| (new_serial.type < PORT_UNKNOWN) || (new_serial.type > PORT_MAX) || (new_serial.type == PORT_CIRRUS) || (new_serial.type == PORT_STARTECH)) { return -EINVAL; } if ((new_serial.type != state->type) || (new_serial.xmit_fifo_size <= 0)) new_serial.xmit_fifo_size = 16; // uart_config[new_serial.type].dfl_xmit_fifo_size; /* Make sure address is not already in use */ if (new_serial.type) { for (i = 0 ; i < NR_PORTS; i++) if ((state != &rs_table[i]) && (rs_table[i].port == new_port) && rs_table[i].type) return -EADDRINUSE; } if ((change_port || change_irq) && (state->count > 1)) return -EBUSY; /* * OK, past this point, all the error checking has been done. * At this point, we start making changes..... */ state->baud_base = new_serial.baud_base; state->flags = ((state->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS)); info->flags = ((state->flags & ~ASYNC_INTERNAL_FLAGS) | (info->flags & ASYNC_INTERNAL_FLAGS)); state->custom_divisor = new_serial.custom_divisor; state->close_delay = new_serial.close_delay * HZ/100; state->closing_wait = new_serial.closing_wait * HZ/100; info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; info->xmit_fifo_size = state->xmit_fifo_size = 16; //=== new_serial.xmit_fifo_size; if ((state->type != PORT_UNKNOWN) && state->port) { release_region(state->port,8); } state->type = new_serial.type; if (change_port || change_irq) { /* * We need to shutdown the serial port at the old * port/irq combination. */ // shutdown(info); state->irq = new_serial.irq; info->port = state->port = new_port; info->hub6 = state->hub6 = new_serial.hub6; if (info->hub6) info->io_type = state->io_type = SERIAL_IO_HUB6; else if (info->io_type == SERIAL_IO_HUB6) info->io_type = state->io_type = SERIAL_IO_PORT; } if ((state->type != PORT_UNKNOWN) && state->port) { request_region(state->port,8,"serial(set)"); } check_and_exit: if (!state->port || !state->type) return 0; if (info->flags & ASYNC_INITIALIZED) { if (((old_state.flags & ASYNC_SPD_MASK) != (state->flags & ASYNC_SPD_MASK)) || (old_state.custom_divisor != state->custom_divisor)) { if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) info->tty->alt_speed = 57600; if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) info->tty->alt_speed = 115200; if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) info->tty->alt_speed = 230400; if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) info->tty->alt_speed = 460800; // change_speed(info, 0); } } else { retval = startup(info); } return retval; } //#endif static int rs_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) { struct async_struct * info = (struct async_struct *)tty->driver_data; struct async_icount cprev, cnow; /* kernel counter temps */ struct serial_icounter_struct icount; unsigned long flags; if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; } switch (cmd) { //case TIOCMGET: // return get_modem_info(info, (unsigned int *) arg); case TIOCMBIS: case TIOCMBIC: //case TIOCMSET: // return set_modem_info(info, cmd, (unsigned int *) arg); case TIOCGSERIAL: return get_serial_info(info, (struct serial_struct *) arg); case TIOCSSERIAL: return set_serial_info(info, (struct serial_struct *) arg); //case TIOCSERCONFIG: // return do_autoconfig(info); //case TIOCSERGETLSR: /* Get line status register */ // return get_lsr_info(info, (unsigned int *) arg); case TIOCSERGSTRUCT: if (copy_to_user((struct async_struct *) arg, info, sizeof(struct async_struct))) return -EFAULT; return 0; /* * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change * - mask passed in arg for lines of interest * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) * Caller should use TIOCGICOUNT to see which one it was */ case TIOCMIWAIT: #if 0 save_flags(flags); cli(); /* note the counters on entry */ cprev = info->state->icount; restore_flags(flags); /* Force modem status interrupts on */ info->IER |= UART_IER_MSI; serial_out(info, UART_IER, info->IER); while (1) { interruptible_sleep_on(&info->delta_msr_wait); /* see if a signal did it */ if (signal_pending(current)) return -ERESTARTSYS; save_flags(flags); cli(); cnow = info->state->icount; /* atomic copy */ restore_flags(flags); if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) return -EIO; /* no change => error */ if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) { return 0; } cprev = cnow; } /* NOTREACHED */ #endif /* * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) * Return: write counters to the user passed counter struct * NB: both 1->0 and 0->1 transitions are counted except for * RI where only 0->1 is counted. */ case TIOCGICOUNT: save_flags(flags); cli(); cnow = info->state->icount; restore_flags(flags); icount.cts = cnow.cts; icount.dsr = cnow.dsr; icount.rng = cnow.rng; icount.dcd = cnow.dcd; icount.rx = cnow.rx; icount.tx = cnow.tx; icount.frame = cnow.frame; icount.overrun = cnow.overrun; icount.parity = cnow.parity; icount.brk = cnow.brk; icount.buf_overrun = cnow.buf_overrun; if (copy_to_user((void *)arg, &icount, sizeof(icount))) return -EFAULT; return 0; case TIOCSERGWILD: case TIOCSERSWILD: /* "setserial -W" is called in Debian boot */ printk ("TIOCSER?WILD ioctl obsolete, ignored.\n"); return 0; default: return -ENOIOCTLCMD; } return 0; } /* * ------------------------------------------------------------ * rs_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------ */ static void rs_throttle(struct tty_struct * tty) { #if 0 struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; #ifdef SERIAL_DEBUG_THROTTLE char buf[64]; printk("throttle %s: %d....\n", tty_name(tty, buf), tty->ldisc.chars_in_buffer(tty)); #endif if (I_IXOFF(tty)) rs_send_xchar(tty, STOP_CHAR(tty)); if (tty->termios->c_cflag & CRTSCTS) info->MCR &= ~UART_MCR_RTS; save_flags(flags); cli(); serial_out(info, UART_MCR, info->MCR); restore_flags(flags); #endif } static void rs_unthrottle(struct tty_struct * tty) { #if 0 struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; #ifdef SERIAL_DEBUG_THROTTLE char buf[64]; printk("unthrottle %s: %d....\n", tty_name(tty, buf), tty->ldisc.chars_in_buffer(tty)); #endif if (I_IXOFF(tty)) { if (info->x_char) info->x_char = 0; else rs_send_xchar(tty, START_CHAR(tty)); } //if (tty->termios->c_cflag & CRTSCTS) // info->MCR |= UART_MCR_RTS; save_flags(flags); cli(); //serial_out(info, UART_MCR, info->MCR); restore_flags(flags); #endif } static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios) { #if 0 struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; unsigned int cflag = tty->termios->c_cflag; if ((cflag == old_termios->c_cflag) && (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) return; /* Handle transition to B0 status */ if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) { info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS); save_flags(flags); cli(); serial_out(info, UART_MCR, info->MCR); restore_flags(flags); } /* Handle transition away from B0 status */ if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { info->MCR |= UART_MCR_DTR; if (!(tty->termios->c_cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags)) { info->MCR |= UART_MCR_RTS; } save_flags(flags); cli(); serial_out(info, UART_MCR, info->MCR); restore_flags(flags); } /* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { tty->hw_stopped = 0; rs_start(tty); } #endif } /* * ------------------------------------------------------------ * rs_stop() and rs_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */ static void rs_stop(struct tty_struct *tty) { struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; save_flags(flags); cli(); if (info->IER & UART_TX_INT_EN) { info->IER &= ~UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); } restore_flags(flags); } static void rs_start(struct tty_struct *tty) { struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; save_flags(flags); cli(); if (info->xmit.head != info->xmit.tail && info->xmit.buf && !(info->IER & UART_TX_INT_EN)) { info->IER |= UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); } restore_flags(flags); } /* * rs_hangup() --- called by tty_hangup() when a hangup is signaled. */ static void rs_hangup(struct tty_struct *tty) { struct async_struct * info = (struct async_struct *)tty->driver_data; struct serial_state *state = info->state; state = info->state; rs_flush_buffer(tty); if (info->flags & ASYNC_CLOSING) return; info->event = 0; state->count = 0; info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE); info->tty = 0; wake_up_interruptible(&info->open_wait); } /* * rs_break() --- routine which turns the break handling on or off */ static void rs_break(struct tty_struct *tty, int break_state) { struct async_struct * info = (struct async_struct *)tty->driver_data; unsigned long flags; if (!CONFIGURED_SERIAL_PORT(info)) return; save_flags(flags); cli(); if (break_state == -1) info->LCR |= UART_SEND_BREAK; else info->LCR &= ~UART_SEND_BREAK; serial_out(info, UART_LCR_H_REG, info->LCR); restore_flags(flags); } /* * This function is used to send a high-priority XON/XOFF character to * the device */ static void rs_send_xchar(struct tty_struct *tty, char ch) { struct async_struct *info = (struct async_struct *)tty->driver_data; info->x_char = ch; if (ch) { /* Make sure transmit interrupts are on */ info->IER |= UART_TX_INT_EN; serial_out(info, UART_CR_REG, info->IER); } } /* * rs_wait_until_sent() --- wait until the transmitter is empty */ static void rs_wait_until_sent(struct tty_struct *tty, int timeout) { // #if 0 struct async_struct * info = (struct async_struct *)tty->driver_data; unsigned long orig_jiffies, char_time; int lsr; if (info->state->type == PORT_UNKNOWN) return; if (info->xmit_fifo_size == 0) return; /* Just in case.... */ orig_jiffies = jiffies; /* * Set the check interval to be 1/5 of the estimated time to * send a single character, and make it at least 1. The check * interval should also be less than the timeout. * * Note: we have to use pretty tight timings here to satisfy * the NIST-PCTS. */ char_time = (info->timeout - HZ/50) / info->xmit_fifo_size; char_time = char_time / 5; if (char_time == 0) char_time = 1; if (timeout && timeout < char_time) char_time = timeout; /* * If the transmitter hasn't cleared in twice the approximate * amount of time to send the entire FIFO, it probably won't * ever clear. This assumes the UART isn't doing flow * control, which is currently the case. Hence, if it ever * takes longer than info->timeout, this is probably due to a * UART bug of some kind. So, we clamp the timeout parameter at * 2*info->timeout. */ if (!timeout || timeout > 2*info->timeout) timeout = 2*info->timeout; #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("In rs_wait_until_sent(%d) check=%lu...", timeout, char_time); printk("jiff=%lu...", jiffies); #endif while (!((lsr = serial_in(info, UART_LSR)) & UART_LSR_TEMT)) { #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("lsr = %d (jiff=%lu)...", lsr, jiffies); #endif set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(char_time); if (signal_pending(current)) break; if (timeout && time_after(jiffies, orig_jiffies + timeout)) break; } set_current_state(TASK_RUNNING); #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies); #endif // #endif } /* * /proc fs routines.... */ static inline int line_info(char *buf, struct serial_state *state) { //#if 0 struct async_struct *info = state->info, scr_info; char stat_buf[30], control, status; int ret; unsigned long flags; ret = sprintf(buf, "%d: uart:%s port:%lX irq:%d", state->line, "unknown", state->port, state->irq); if (!state->port || (state->type == PORT_UNKNOWN)) { ret += sprintf(buf+ret, "\n"); return ret; } /* * Figure out the current RS-232 lines */ if (!info) { info = &scr_info; /* This is just for serial_{in,out} */ info->magic = SERIAL_MAGIC; info->port = state->port; info->flags = state->flags; info->quot = 0; info->tty = 0; } save_flags(flags); cli(); status = serial_in(info, UART_MSR); control = info != &scr_info ? info->MCR : serial_in(info, UART_MCR); restore_flags(flags); stat_buf[0] = 0; stat_buf[1] = 0; if (control & UART_MCR_RTS) strcat(stat_buf, "|RTS"); if (status & UART_MSR_CTS) strcat(stat_buf, "|CTS"); if (control & UART_MCR_DTR) strcat(stat_buf, "|DTR"); if (status & UART_MSR_DSR) strcat(stat_buf, "|DSR"); if (status & UART_MSR_DCD) strcat(stat_buf, "|CD"); if (status & UART_MSR_RI) strcat(stat_buf, "|RI"); if (info->quot) { ret += sprintf(buf+ret, " baud:%d", state->baud_base / info->quot); } ret += sprintf(buf+ret, " tx:%d rx:%d", state->icount.tx, state->icount.rx); if (state->icount.frame) ret += sprintf(buf+ret, " fe:%d", state->icount.frame); if (state->icount.parity) ret += sprintf(buf+ret, " pe:%d", state->icount.parity); if (state->icount.brk) ret += sprintf(buf+ret, " brk:%d", state->icount.brk); if (state->icount.overrun) ret += sprintf(buf+ret, " oe:%d", state->icount.overrun); /* * Last thing is the RS-232 status lines */ ret += sprintf(buf+ret, " %s\n", stat_buf+1); return ret; //#endif return 0; } int rs_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int i, len = 0, l; off_t begin = 0; len += sprintf(page, "serinfo:1.0 driver:%s%s revision:%s\n", serial_version, LOCAL_VERSTRING, serial_revdate); for (i = 0; i < NR_PORTS && len < 4000; i++) { l = line_info(page + len, &rs_table[i]); len += l; if (len+begin > off+count) goto done; if (len+begin < off) { begin += len; len = 0; } } *eof = 1; done: if (off >= len+begin) return 0; *start = page + (off-begin); return ((count < begin+len-off) ? count : begin+len-off); } /* * This routine must be called by kernel at boot time */ int serial5120_init(void) { memset(&dev_tty_driver, 0, sizeof(struct tty_driver)); dev_tty_driver.magic = TTY_DRIVER_MAGIC; dev_tty_driver.driver_name = "serial"; dev_tty_driver.name = "ttyS"; dev_tty_driver.major = TTY_MAJOR; dev_tty_driver.minor_start = 64; dev_tty_driver.num = 2; dev_tty_driver.type = TTY_DRIVER_TYPE_SERIAL; dev_tty_driver.subtype = SERIAL_TYPE_NORMAL; dev_tty_driver.init_termios = tty_std_termios; dev_tty_driver.init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; dev_tty_driver.flags = TTY_DRIVER_REAL_RAW /*| TTY_DRIVER_NO_DEVFS*/; dev_tty_driver.refcount = &serial_refcount; dev_tty_driver.table = serial_table; dev_tty_driver.termios = serial_termios; dev_tty_driver.termios_locked = serial_termios_locked; dev_tty_driver.open = rs_open; dev_tty_driver.close = rs_close; dev_tty_driver.write = rs_write; dev_tty_driver.put_char = rs_put_char; dev_tty_driver.flush_chars = rs_flush_chars; dev_tty_driver.write_room = rs_write_room; dev_tty_driver.chars_in_buffer = rs_chars_in_buffer; dev_tty_driver.flush_buffer = rs_flush_buffer; dev_tty_driver.ioctl = rs_ioctl; dev_tty_driver.throttle = rs_throttle; dev_tty_driver.unthrottle = rs_unthrottle; dev_tty_driver.set_termios = rs_set_termios; dev_tty_driver.stop = rs_stop; dev_tty_driver.start = rs_start; dev_tty_driver.hangup = rs_hangup; dev_tty_driver.break_ctl = rs_break; dev_tty_driver.send_xchar = rs_send_xchar; dev_tty_driver.wait_until_sent = rs_wait_until_sent; dev_tty_driver.read_proc = rs_read_proc; if (tty_register_driver(&dev_tty_driver)) panic("Couldn't register /dev/ttyS0 driver\n"); return 0; }