From: Dave A. <ai...@us...> - 2002-10-25 17:35:58
|
Update of /cvsroot/linux-vax/kernel-2.4/drivers/vax/char In directory usw-pr-cvs1:/tmp/cvs-serv27297/char Added Files: Makefile iprcons.c Log Message: DA: backport from 2.5 of Kenns qbus/delqa work - delqa/qbus works iprcons is not working.. needed for SIMH emu --- NEW FILE --- # # Makefile for the Linux/VAX character device drivers. # O_TARGET := vaxchar.o obj-y := obj-m := obj-n := obj-$(CONFIG_SERIAL_IPR) += iprcons.o include $(TOPDIR)/Rules.make --- NEW FILE --- /* $Id: iprcons.c,v 1.1 2002/10/25 14:04:35 airlied Exp $ Copyright (c) 2002, Kenn Humborg Serial driver for VAX console ports that are accessed through internal processor registers (IPRs). "Real" VAX CPUs use 4 internal registers to drive the console serial port (this info is from the VAX Architecture Reference Manual): RXCS Console Receive Control and Status Register Bit 7 RDY (ready, read-only) Cleared by processor initialization and by reading RXDB. When RDY is clear, RXDB is unpredictable. When RDY is set, RXDB contains valid data to be read. Bit 6 IE (interrupt enable, read/write) Cleared by processor initialization and by being written zero. If IE is set by software while RXDB RDY is already set, or if RDY is set by the console while IE is already set, then an interrupt is requested at IPL 14 (hex). That is, an interrupt is requested when the function (IE and RDY) changes from 0 to 1. RXDB Console Receive Data Buffer Register Bit 15 ERROR (read-only) An error occurred while receiving data, such as data overrun or loss of carrier. Cleared by processor initialization and by reading from RXDB. Bits 11:8 ID (read-only) If zero, then data is from the console terminal. If nonzero, then the rest of the register is implementation dependent. Cleared by processor initialization and by reading from RXDB. Bits 7:0 DATA (read-only) Data from the console terminal (if ID is zero). Unpredictable unless RXCS RDY is set. TXCS Console Transmit Control and Status Register Bit 7 RDY (read-only) Set by processor initialization. RDY is clear when the console terminal is busy writing a character written to TXDB. RDY is set when the console terminal is ready to receive another character. Bit 6 IE (interrupt enable, read/write) Cleared by processor initialization and by being written clear. If IE is set by software while RXDB RDY is already set, or if RDY is set by the console while IE is already set, then an interrupt is requested at IPL 14 (hex). That is, an interrupt is requested when the function (IE and RDY) changes from 0 to 1. TXDB Console Transmit Data Buffer Register Bits 11:8 ID (read/write) If ID is written zero when TXDB is written, the data goes to the console terminal. If ID is written with 0F (hex), the data is a message to be sent to the console. If ID is neither zero nor 0F (hex), the meaning is implementation-dependent. Bits 7:0 DATA (read/write) If ID is zero, the data is a character sent to the console terminal to type. If ID is 0F (hex), the data is a message to be sent to the console, with the following meaning: 1. Software done -- A program started by a console indirect command file is signalling successful completion. When the processor halts, the consolel should resume processing the indirect command file. 2. Boot processor -- The console should initiate a system bootstrap. 3. Clear "restart in progress" flag -- A system restart has been successfully completed. If a system restart would occur automatically, the attempt should be allowed. 4. Clear "bootstrap in progress" flag -- A system bootstrap has successfully completed. If a system bootstrap would occur automatically, the attempt should be allowed. However, the implementation of these registers varies a little bit between CPUs. For example, the KA650/655 uses the ID fields a bit differently. To handle this, this driver will evetually become a "template" driver (like drivers/scsi/NCR5380.c) which will be #include-d in individual drivers for each CPU's console port. It will provide all the generic functionality, and provide callouts for any implementation-specific functionality. TODO: o Write iprcons_set_termios(). I don't think we need to do anything here, since there is nothing configurable on these ports. o ioctl() o Write transmit code path: iprcons_write(), iprcons_put_char(), iprcons_flush_chars() and iprcons_write_root(), iprcons_start() and iprcons_stop(), iprcons_wait_until_sent(), iprcons_send_xchar(). o Write TX ISR. o Write receive code path - RX ISR o Write iprcons_close(). Release interrupts and uninit. o Write iprcons_break_ctl(). o SMP/pre-emptive issues o system console support */ #include <linux/version.h> #include <linux/module.h> #include <linux/config.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/major.h> #include <linux/delay.h> #include <linux/param.h> #include <linux/tqueue.h> #include <linux/interrupt.h> #include <linux/serial.h> #include <linux/serialP.h> #include <linux/generic_serial.h> /* for definition of struct console */ #if defined(CONFIG_SERIAL_CONSOLE) #include <linux/console.h> #include <asm/mv.h> /* for mtpr_putchar */ #endif /* if defined(CONFIG_SERIAL_CONSOLE) */ #include <linux/tty.h> #include <linux/tty_flip.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/mtpr.h> #define IPRCONS_XMIT_SIZE 4096 /* buffer size */ #define WAKEUP_CHARS IPRCONS_XMIT_SIZE/4 #define IPRCONS_EVENT_WRITE_WAKEUP 0 #define IPRCONS_CLOSING_WAIT_INF 0 #define IPRCONS_CLOSING_WAIT_NONE 65535 struct iprcons_serial { struct gs_port gs; /* must be first in structure */ int type; unsigned short x_char; /* pending xon/xoff character */ struct async_icount icount; /* keep track of things ... */ struct tq_struct tqueue; /* Queue for BH */ unsigned long event; /* mask used in BH */ unsigned char is_console; /* flag indicating a serial console */ }; static struct iprcons_serial console_state; static struct tty_driver iprcons_serial_driver, iprcons_callout_driver; static struct tty_struct *iprcons_table[1]; static struct termios *iprcons_termios[1]; static struct termios *iprcons_termios_locked[1]; static int iprcons_refcount; /* Register definitions */ #define PR_RXCS_RDY 0x0080 #define PR_RXCS_IE 0x0040 #define PR_RXDB_ERROR 0x8000 #define PR_RXDB_ID 0x0f00 #define PR_RXDB_DATA 0x00ff #define PR_TXCS_RDY 0x0080 #define PR_TXCS_IE 0x0040 #define PR_TXDB_ID 0x0f00 #define PR_TXDB_DATA 0x00ff /* These vectors are defined by the VAX Architecture Reference Manual */ #define IPRCONS_RX_VECTOR 0x3e #define IPRCONS_TX_VECTOR 0x3f static void iprcons_enable_rx_interrupts(void *ptr) { __mtpr(PR_RXCS_IE, PR_RXCS); } static void iprcons_disable_rx_interrupts(void *ptr) { __mtpr(0, PR_RXCS); } static void iprcons_enable_tx_interrupts(void *ptr) { __mtpr(PR_TXCS_IE, PR_TXCS); } static void iprcons_disable_tx_interrupts(void *ptr) { __mtpr(0, PR_TXCS); } static int iprcons_get_CD(void *ptr) { /* Our hardware doesn't have a CD line, so pretend that CD is always asserted */ return 1; } /* * ------------------------------------------------------------ * iprcons_sched_event () * * This routine is used by the interrupt handler to schedule * processing in the software interrupt portion of the driver. * ------------------------------------------------------------ */ static inline void iprcons_sched_event (struct iprcons_serial *port, int event) { port->event |= 1 << event; queue_task(&port->tqueue, &tq_immediate); mark_bh(IMMEDIATE_BH); } /* * ------------------------------------------------------------ * receive_char () * * This routine deals with input from the port * ------------------------------------------------------------ */ static inline void iprcons_rx_char(struct iprcons_serial *port, unsigned int rxcs, unsigned int rxdb) { struct tty_struct *tty = port->gs.tty; unsigned char ch = rxdb & PR_RXDB_DATA; if (tty->flip.count >= TTY_FLIPBUF_SIZE) { /* No space for this character */ return; } *tty->flip.char_buf_ptr = ch; *tty->flip.flag_buf_ptr = TTY_NORMAL; port->icount.rx++; /* FIXME: provide a hook here for individual CPUs to interpret their error bits and update the stats and the flag_buf flags. */ tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; tty_flip_buffer_push(tty); } static inline void iprcons_outchar(unsigned char c) { __mtpr(c, PR_TXDB); } static inline void iprcons_tx_ready(struct iprcons_serial *port) { if (port->x_char) { /* XON/XOFF chars */ iprcons_outchar(port->x_char); port->icount.tx++; port->x_char = 0; return; } /* if nothing to do or stopped */ if (port->gs.xmit_cnt <= 0) { port->gs.flags &= ~GS_TX_INTEN; iprcons_disable_tx_interrupts(port); printk("TX interrupt: nothing to transmit\n"); return; } /* If there is anything waiting, send it now */ // iprcons_outchar(port->gs.xmit_buf[port->gs.xmit_tail]); mtpr_putchar(port->gs.xmit_buf[port->gs.xmit_tail]); // printk("TX interrupt: would transmit %02x - %c\n", // port->gs.xmit_buf[port->gs.xmit_tail], // port->gs.xmit_buf[port->gs.xmit_tail]); port->gs.xmit_tail = (port->gs.xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1); port->gs.xmit_cnt--; port->icount.tx++; if (port->gs.xmit_cnt <= port->gs.wakeup_chars) { iprcons_sched_event(port, IPRCONS_EVENT_WRITE_WAKEUP); } if (port->gs.xmit_cnt <= 0) { port->gs.flags &= ~GS_TX_INTEN; iprcons_disable_tx_interrupts(port); } } static void iprcons_rx_interrupt (int irq, void *dev, struct pt_regs *regs) { unsigned int rxcs = __mfpr(PR_RXCS); unsigned int rxdb = __mfpr(PR_RXDB); struct iprcons_serial *port = dev; // printk("iprcons_rx_interrupt: RXCS=%04x RXDB=%04x\n", rxcs, rxdb); if (port->gs.flags & GS_ACTIVE) { if (rxcs & PR_RXCS_RDY) { iprcons_rx_char(port, rxcs, rxdb); } } else { iprcons_disable_rx_interrupts(port); } } static void iprcons_tx_interrupt (int irq, void *dev, struct pt_regs *regs) { unsigned int txcs = __mfpr(PR_TXCS); struct iprcons_serial *port = dev; // printk("iprcons_tx_interrupt: TXCS=%04x\n", txcs); if (port->gs.flags & GS_ACTIVE) { if (txcs & PR_TXCS_RDY) { iprcons_tx_ready(port); } } else { iprcons_disable_tx_interrupts(port); } } static void do_softint(void *ptr) { struct iprcons_serial *port = ptr; struct tty_struct *tty; tty = port->gs.tty; if (!tty) return; if (test_and_clear_bit(IPRCONS_EVENT_WRITE_WAKEUP, &port->event)) { if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); } } /* * ------------------------------------------------------------------- * iprcons_chars_in_buffer () * * compute the amount of characters in the _hardware's_ output buffer * ------------------------------------------------------------------- */ static int iprcons_chars_in_buffer (void *ptr) { if (__mfpr(PR_TXCS) & PR_TXCS_RDY) { /* output buffer must be empty */ return 0; } else { /* Output buffer must contain 1 character */ return 1; } } /* * ------------------------------------------------------------ * iprcons_throttle () and iprcons_unthrottle () * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled (or not). * ------------------------------------------------------------ */ static void iprcons_throttle (struct tty_struct *tty) { struct iprcons_serial *port = tty->driver_data; if (I_IXOFF(tty)) { port->x_char = STOP_CHAR(tty); } } static void iprcons_unthrottle (struct tty_struct *tty) { struct iprcons_serial *port = tty->driver_data; if (I_IXOFF(tty)) { if (port->x_char) { port->x_char = 0; } else { port->x_char = START_CHAR(tty); } } } static void iprcons_send_xchar (struct tty_struct *tty, char ch) { struct iprcons_serial *port = tty->driver_data; port->x_char = ch; if (ch) { gs_start(port->gs.tty); } } /* * ------------------------------------------------------------ * iprcons_ioctl () and friends * ------------------------------------------------------------ */ static int iprcons_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) { struct iprcons_serial *port = tty->driver_data; int retval; retval = 0; switch (cmd) { case TIOCGSOFTCAR: retval = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *)arg); break; case TIOCSSOFTCAR: retval = get_user (arg, (unsigned long *)arg); if (retval == 0) { tty->termios->c_cflag = (tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0); } break; case TIOCGSERIAL: retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct serial_struct)); if (retval == 0) { gs_getserial(&port->gs, (struct serial_struct *) arg); } break; case TIOCSSERIAL: retval = verify_area(VERIFY_READ, (void *) arg, sizeof(struct serial_struct)); if (retval == 0) { retval = gs_setserial(&port->gs, (struct serial_struct *) arg); } break; default: return -ENOIOCTLCMD; } return 0; } /* * ------------------------------------------------------------------- * iprcons_request_irqs() * * This routine is called during the first open() on the port to * hook interrupts. * ------------------------------------------------------------------- */ static int iprcons_request_irqs(struct iprcons_serial *port) { unsigned int retval; unsigned long flags; save_and_cli(flags); retval = request_irq(IPRCONS_TX_VECTOR, iprcons_tx_interrupt, 0, "iprcons-tx", port); if (retval) { printk("iprcons: unable to acquire TX interrupt vector\n"); } else { retval = request_irq(IPRCONS_RX_VECTOR, iprcons_rx_interrupt, 0, "iprcons-rx", port); if (retval) { free_irq(IPRCONS_TX_VECTOR, port); printk("iprcons: unable to acquire RX interrupt vector\n"); } } restore_flags (flags); return retval; } static void iprcons_free_irqs(struct iprcons_serial *port) { free_irq(IPRCONS_TX_VECTOR, port); free_irq(IPRCONS_RX_VECTOR, port); } static void iprcons_shutdown_port(void *ptr) { struct iprcons_serial *port = ptr; port->gs.flags &= ~GS_ACTIVE; iprcons_free_irqs(port); } static int iprcons_set_real_termios(void *ptr) { return 0; } static void iprcons_hungup(void *ptr) { MOD_DEC_USE_COUNT; } static void iprcons_close(void *ptr) { MOD_DEC_USE_COUNT; //printk("iprcons_close: count = %d\n", ((struct iprcons_serial *)ptr)->gs.count); } static struct real_driver iprcons_real_driver = { iprcons_disable_tx_interrupts, iprcons_enable_tx_interrupts, iprcons_disable_rx_interrupts, iprcons_enable_rx_interrupts, iprcons_get_CD, iprcons_shutdown_port, iprcons_set_real_termios, iprcons_chars_in_buffer, iprcons_close, iprcons_hungup, NULL }; /* * This routine is called whenever a serial port is opened. It * enables interrupts for a serial port. It also performs the * serial-specific initialization for the tty structure. */ static int iprcons_open (struct tty_struct *tty, struct file *filp) { struct iprcons_serial *port; int retval; port = &console_state; tty->driver_data = port; port->gs.tty = tty; port->gs.count++; port->event = 0; port->tqueue.routine = do_softint; port->tqueue.data = port; //printk("iprcons_open: entered: count = %d\n", port->gs.count); retval = gs_init_port(&port->gs); if (retval) { goto failed_1; } port->gs.flags |= GS_ACTIVE; if (port->gs.count == 1) { MOD_INC_USE_COUNT; retval = iprcons_request_irqs(port); if (retval) { goto failed_2; } } retval = gs_block_til_ready(port, filp); if (retval) { goto failed_3; } if ((port->gs.count == 1) && (port->gs.flags & ASYNC_SPLIT_TERMIOS)) { if (tty->driver.subtype == SERIAL_TYPE_NORMAL) { *tty->termios = port->gs.normal_termios; } else { *tty->termios = port->gs.callout_termios; } } iprcons_enable_rx_interrupts(port); port->gs.session = current->session; port->gs.pgrp = current->pgrp; return 0; failed_3: iprcons_free_irqs(port); failed_2: MOD_DEC_USE_COUNT; failed_1: port->gs.count--; return retval; } int __init iprcons_init(void) { /* Should really check for CPUs with IPR-based consoles here */ int error; struct iprcons_serial *port; memset(&iprcons_serial_driver, 0, sizeof(iprcons_serial_driver)); iprcons_serial_driver.magic = TTY_DRIVER_MAGIC; iprcons_serial_driver.name = "ttyS"; iprcons_serial_driver.major = TTY_MAJOR; iprcons_serial_driver.minor_start = 64; iprcons_serial_driver.num = 1; iprcons_serial_driver.type = TTY_DRIVER_TYPE_SERIAL; iprcons_serial_driver.subtype = SERIAL_TYPE_NORMAL; iprcons_serial_driver.init_termios = tty_std_termios; iprcons_serial_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; iprcons_serial_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; iprcons_serial_driver.refcount = &iprcons_refcount; iprcons_serial_driver.table = iprcons_table; iprcons_serial_driver.termios = iprcons_termios; iprcons_serial_driver.termios_locked = iprcons_termios_locked; iprcons_serial_driver.open = iprcons_open; iprcons_serial_driver.close = gs_close; iprcons_serial_driver.write = gs_write; iprcons_serial_driver.put_char = gs_put_char; iprcons_serial_driver.flush_chars = gs_flush_chars; iprcons_serial_driver.write_room = gs_write_room; iprcons_serial_driver.chars_in_buffer = gs_chars_in_buffer; iprcons_serial_driver.flush_buffer = gs_flush_buffer; iprcons_serial_driver.ioctl = iprcons_ioctl; iprcons_serial_driver.throttle = iprcons_throttle; iprcons_serial_driver.unthrottle = iprcons_unthrottle; iprcons_serial_driver.send_xchar = iprcons_send_xchar; iprcons_serial_driver.stop = gs_stop; iprcons_serial_driver.start = gs_start; iprcons_serial_driver.hangup = gs_hangup; /* * The callout device is just like normal device except for major * number and the subtype code. */ iprcons_callout_driver = iprcons_serial_driver; iprcons_callout_driver.name = "cua"; iprcons_callout_driver.major = TTYAUX_MAJOR; iprcons_callout_driver.subtype = SERIAL_TYPE_CALLOUT; error = tty_register_driver (&iprcons_serial_driver); if (error) { printk("iprcons: Couldn't register serial driver: error = %d\n", error); return 1; } error = tty_register_driver (&iprcons_callout_driver); if (error) { tty_unregister_driver(&iprcons_serial_driver); printk("iprcons: Couldn't register callout driver: error = %d\n", error); return 1; } port = &console_state; memset(port, 0, sizeof(*port)); port->gs.magic = SERIAL_MAGIC; port->gs.callout_termios = iprcons_callout_driver.init_termios; port->gs.normal_termios = iprcons_serial_driver.init_termios; port->gs.close_delay = HZ/2; port->gs.closing_wait = 30 * HZ; init_waitqueue_head(&port->gs.open_wait); init_waitqueue_head(&port->gs.close_wait); port->gs.rd = &iprcons_real_driver; printk("ttyS0: Internal processor register console\n"); return 0; } __initcall(iprcons_init); /* End of regular serial port support - the rest of this file is related to serial console (printk) support */ #ifdef CONFIG_SERIAL_CONSOLE static void iprcons_console_print(struct console *cons, const char *str, unsigned int count) { unsigned int old_ie; old_ie = __mfpr(PR_TXCS) & PR_TXCS_IE; /* Ensure TX interrupts are disabled */ __mtpr(0, PR_TXCS); while (count--) { if (*str == '\n') { mtpr_putchar('\r'); } mtpr_putchar(*str++); } /* Enable TX interrupts again if necessary */ __mtpr(old_ie, PR_TXCS); } static kdev_t iprcons_console_device(struct console *cons) { return mk_kdev(TTY_MAJOR, 64); } static struct console iprcons_sercons = { name: "ttyS", write: iprcons_console_print, device: iprcons_console_device, flags: CON_CONSDEV | CON_PRINTBUFFER, index: 0, }; void __init iprcons_serial_console_init(void) { register_console(&iprcons_sercons); } #endif /* ifdef CONFIG_SERIAL_CONSOLE */ |