/* * SCLP line mode terminal driver. * * S390 version * Copyright IBM Corp. 1999 * Author(s): Martin Peschke <mpeschke@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> */ #include <linux/module.h> #include <linux/kmod.h> #include <linux/tty.h> #include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/err.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/gfp.h> #include <asm/uaccess.h> #include "ctrlchar.h" #include "sclp.h" #include "sclp_rw.h" #include "sclp_tty.h" /* * size of a buffer that collects single characters coming in * via sclp_tty_put_char() */ #define SCLP_TTY_BUF_SIZE 512 /* * There is exactly one SCLP terminal, so we can keep things simple * and allocate all variables statically. */ /* Lock to guard over changes to global variables. */ static spinlock_t sclp_tty_lock; /* List of free pages that can be used for console output buffering. */ static struct list_head sclp_tty_pages; /* List of full struct sclp_buffer structures ready for output. */ static struct list_head sclp_tty_outqueue; /* Counter how many buffers are emitted. */ static int sclp_tty_buffer_count; /* Pointer to current console buffer. */ static struct sclp_buffer *sclp_ttybuf; /* Timer for delayed output of console messages. */ static struct timer_list sclp_tty_timer; static struct tty_port sclp_port; static unsigned char sclp_tty_chars[SCLP_TTY_BUF_SIZE]; static unsigned short int sclp_tty_chars_count; struct tty_driver *sclp_tty_driver; static int sclp_tty_tolower; static int sclp_tty_columns = 80; #define SPACES_PER_TAB 8 #define CASE_DELIMITER 0x6c /* to separate upper and lower case (% in EBCDIC) */ /* This routine is called whenever we try to open a SCLP terminal. */ static int sclp_tty_open(struct tty_struct *tty, struct file *filp) { tty_port_tty_set(&sclp_port, tty); tty->driver_data = NULL; sclp_port.low_latency = 0; return 0; } /* This routine is called when the SCLP terminal is closed. */ static void sclp_tty_close(struct tty_struct *tty, struct file *filp) { if (tty->count > 1) return; tty_port_tty_set(&sclp_port, NULL); } /* * This routine returns the numbers of characters the tty driver * will accept for queuing to be written. This number is subject * to change as output buffers get emptied, or if the output flow * control is acted. This is not an exact number because not every * character needs the same space in the sccb. The worst case is * a string of newlines. Every newlines creates a new mto which * needs 8 bytes. */ static int sclp_tty_write_room (struct tty_struct *tty) { unsigned long flags; struct list_head *l; int count; spin_lock_irqsave(&sclp_tty_lock, flags); count = 0; if (sclp_ttybuf != NULL) count = sclp_buffer_space(sclp_ttybuf) / sizeof(struct mto); list_for_each(l, &sclp_tty_pages) count += NR_EMPTY_MTO_PER_SCCB; spin_unlock_irqrestore(&sclp_tty_lock, flags); return count; } static void sclp_ttybuf_callback(struct sclp_buffer *buffer, int rc) { unsigned long flags; void *page; do { page = sclp_unmake_buffer(buffer); spin_lock_irqsave(&sclp_tty_lock, flags); /* Remove buffer from outqueue */ list_del(&buffer->list); sclp_tty_buffer_count--; list_add_tail((struct list_head *) page, &sclp_tty_pages); /* Check if there is a pending buffer on the out queue. */ buffer = NULL; if (!list_empty(&sclp_tty_outqueue)) buffer = list_entry(sclp_tty_outqueue.next, struct sclp_buffer, list); spin_unlock_irqrestore(&sclp_tty_lock, flags); } while (buffer && sclp_emit_buffer(buffer, sclp_ttybuf_callback)); tty_port_tty_wakeup(&sclp_port); } static inline void __sclp_ttybuf_emit(struct sclp_buffer *buffer) { unsigned long flags; int count; int rc; spin_lock_irqsave(&sclp_tty_lock, flags); list_add_tail(&buffer->list, &sclp_tty_outqueue); count = sclp_tty_buffer_count++; spin_unlock_irqrestore(&sclp_tty_lock, flags); if (count) return; rc = sclp_emit_buffer(buffer, sclp_ttybuf_callback); if (rc) sclp_ttybuf_callback(buffer, rc); } /* * When this routine is called from the timer then we flush the * temporary write buffer. */ static void sclp_tty_timeout(unsigned long data) { unsigned long flags; struct sclp_buffer *buf; spin_lock_irqsave(&sclp_tty_lock, flags); buf = sclp_ttybuf; sclp_ttybuf = NULL; spin_unlock_irqrestore(&sclp_tty_lock, flags); if (buf != NULL) { __sclp_ttybuf_emit(buf); } } /* * Write a string to the sclp tty. */ static int sclp_tty_write_string(const unsigned char *str, int count, int may_fail) { unsigned long flags; void *page; int written; int overall_written; struct sclp_buffer *buf; if (count <= 0) return 0; overall_written = 0; spin_lock_irqsave(&sclp_tty_lock, flags); do { /* Create a sclp output buffer if none exists yet */ if (sclp_ttybuf == NULL) { while (list_empty(&sclp_tty_pages)) { spin_unlock_irqrestore(&sclp_tty_lock, flags); if (may_fail) goto out; else sclp_sync_wait(); spin_lock_irqsave(&sclp_tty_lock, flags); } page = sclp_tty_pages.next; list_del((struct list_head *) page); sclp_ttybuf = sclp_make_buffer(page, sclp_tty_columns, SPACES_PER_TAB); } /* try to write the string to the current output buffer */ written = sclp_write(sclp_ttybuf, str, count); overall_written += written; if (written == count) break; /* * Not all characters could be written to the current * output buffer. Emit the buffer, create a new buffer * and then output the rest of the string. */ buf = sclp_ttybuf; sclp_ttybuf = NULL; spin_unlock_irqrestore(&sclp_tty_lock, flags); __sclp_ttybuf_emit(buf); spin_lock_irqsave(&sclp_tty_lock, flags); str += written; count -= written; } while (count > 0); /* Setup timer to output current console buffer after 1/10 second */ if (sclp_ttybuf && sclp_chars_in_buffer(sclp_ttybuf) && !timer_pending(&sclp_tty_timer)) { init_timer(&sclp_tty_timer); sclp_tty_timer.function = sclp_tty_timeout; sclp_tty_timer.data = 0UL; sclp_tty_timer.expires = jiffies + HZ/10; add_timer(&sclp_tty_timer); } spin_unlock_irqrestore(&sclp_tty_lock, flags); out: return overall_written; } /* * This routine is called by the kernel to write a series of characters to the * tty device. The characters may come from user space or kernel space. This * routine will return the number of characters actually accepted for writing. */ static int sclp_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { if (sclp_tty_chars_count > 0) { sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); sclp_tty_chars_count = 0; } return sclp_tty_write_string(buf, count, 1); } /* * This routine is called by the kernel to write a single character to the tty * device. If the kernel uses this routine, it must call the flush_chars() * routine (if defined) when it is done stuffing characters into the driver. * * Characters provided to sclp_tty_put_char() are buffered by the SCLP driver. * If the given character is a '\n' the contents of the SCLP write buffer * - including previous characters from sclp_tty_put_char() and strings from * sclp_write() without final '\n' - will be written. */ static int sclp_tty_put_char(struct tty_struct *tty, unsigned char ch) { sclp_tty_chars[sclp_tty_chars_count++] = ch; if (ch == '\n' || sclp_tty_chars_count >= SCLP_TTY_BUF_SIZE) { sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); sclp_tty_chars_count = 0; } return 1; } /* * This routine is called by the kernel after it has written a series of * characters to the tty device using put_char(). */ static void sclp_tty_flush_chars(struct tty_struct *tty) { if (sclp_tty_chars_count > 0) { sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); sclp_tty_chars_count = 0; } } /* * This routine returns the number of characters in the write buffer of the * SCLP driver. The provided number includes all characters that are stored * in the SCCB (will be written next time the SCLP is not busy) as well as * characters in the write buffer (will not be written as long as there is a * final line feed missing). */ static int sclp_tty_chars_in_buffer(struct tty_struct *tty) { unsigned long flags; struct list_head *l; struct sclp_buffer *t; int count; spin_lock_irqsave(&sclp_tty_lock, flags); count = 0; if (sclp_ttybuf != NULL) count = sclp_chars_in_buffer(sclp_ttybuf); list_for_each(l, &sclp_tty_outqueue) { t = list_entry(l, struct sclp_buffer, list); count += sclp_chars_in_buffer(t); } spin_unlock_irqrestore(&sclp_tty_lock, flags); return count; } /* * removes all content from buffers of low level driver */ static void sclp_tty_flush_buffer(struct tty_struct *tty) { if (sclp_tty_chars_count > 0) { sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); sclp_tty_chars_count = 0; } } /* * push input to tty */ static void sclp_tty_input(unsigned char* buf, unsigned int count) { struct tty_struct *tty = tty_port_tty_get(&sclp_port); unsigned int cchar; /* * If this tty driver is currently closed * then throw the received input away. */ if (tty == NULL) return; cchar = ctrlchar_handle(buf, count, tty); switch (cchar & CTRLCHAR_MASK) { case CTRLCHAR_SYSRQ: break; case CTRLCHAR_CTRL: tty_insert_flip_char(&sclp_port, cchar, TTY_NORMAL); tty_flip_buffer_push(&sclp_port); break; case CTRLCHAR_NONE: /* send (normal) input to line discipline */ if (count < 2 || (strncmp((const char *) buf + count - 2, "^n", 2) && strncmp((const char *) buf + count - 2, "\252n", 2))) { /* add the auto \n */ tty_insert_flip_string(&sclp_port, buf, count); tty_insert_flip_char(&sclp_port, '\n', TTY_NORMAL); } else tty_insert_flip_string(&sclp_port, buf, count - 2); tty_flip_buffer_push(&sclp_port); break; } tty_kref_put(tty); } /* * get a EBCDIC string in upper/lower case, * find out characters in lower/upper case separated by a special character, * modifiy original string, * returns length of resulting string */ static int sclp_switch_cases(unsigned char *buf, int count) { unsigned char *ip, *op; int toggle; /* initially changing case is off */ toggle = 0; ip = op = buf; while (count-- > 0) { /* compare with special character */ if (*ip == CASE_DELIMITER) { /* followed by another special character? */ if (count && ip[1] == CASE_DELIMITER) { /* * ... then put a single copy of the special * character to the output string */ *op++ = *ip++; count--; } else /* * ... special character follower by a normal * character toggles the case change behaviour */ toggle = ~toggle; /* skip special character */ ip++; } else /* not the special character */ if (toggle) /* but case switching is on */ if (sclp_tty_tolower) /* switch to uppercase */ *op++ = _ebc_toupper[(int) *ip++]; else /* switch to lowercase */ *op++ = _ebc_tolower[(int) *ip++]; else /* no case switching, copy the character */ *op++ = *ip++; } /* return length of reformatted string. */ return op - buf; } static void sclp_get_input(struct gds_subvector *sv) { unsigned char *str; int count; str = (unsigned char *) (sv + 1); count = sv->length - sizeof(*sv); if (sclp_tty_tolower) EBC_TOLOWER(str, count); count = sclp_switch_cases(str, count); /* convert EBCDIC to ASCII (modify original input in SCCB) */ sclp_ebcasc_str(str, count); /* transfer input to high level driver */ sclp_tty_input(str, count); } static inline void sclp_eval_selfdeftextmsg(struct gds_subvector *sv) { void *end; end = (void *) sv + sv->length; for (sv = sv + 1; (void *) sv < end; sv = (void *) sv + sv->length) if (sv->key == 0x30) sclp_get_input(sv); } static inline void sclp_eval_textcmd(struct gds_vector *v) { struct gds_subvector *sv; void *end; end = (void *) v + v->length; for (sv = (struct gds_subvector *) (v + 1); (void *) sv < end; sv = (void *) sv + sv->length) if (sv->key == GDS_KEY_SELFDEFTEXTMSG) sclp_eval_selfdeftextmsg(sv); } static inline void sclp_eval_cpmsu(struct gds_vector *v) { void *end; end = (void *) v + v->length; for (v = v + 1; (void *) v < end; v = (void *) v + v->length) if (v->gds_id == GDS_ID_TEXTCMD) sclp_eval_textcmd(v); } static inline void sclp_eval_mdsmu(struct gds_vector *v) { v = sclp_find_gds_vector(v + 1, (void *) v + v->length, GDS_ID_CPMSU); if (v) sclp_eval_cpmsu(v); } static void sclp_tty_receiver(struct evbuf_header *evbuf) { struct gds_vector *v; v = sclp_find_gds_vector(evbuf + 1, (void *) evbuf + evbuf->length, GDS_ID_MDSMU); if (v) sclp_eval_mdsmu(v); } static void sclp_tty_state_change(struct sclp_register *reg) { } static struct sclp_register sclp_input_event = { .receive_mask = EVTYP_OPCMD_MASK | EVTYP_PMSGCMD_MASK, .state_change_fn = sclp_tty_state_change, .receiver_fn = sclp_tty_receiver }; static const struct tty_operations sclp_ops = { .open = sclp_tty_open, .close = sclp_tty_close, .write = sclp_tty_write, .put_char = sclp_tty_put_char, .flush_chars = sclp_tty_flush_chars, .write_room = sclp_tty_write_room, .chars_in_buffer = sclp_tty_chars_in_buffer, .flush_buffer = sclp_tty_flush_buffer, }; static int __init sclp_tty_init(void) { struct tty_driver *driver; void *page; int i; int rc; if (!CONSOLE_IS_SCLP) return 0; driver = alloc_tty_driver(1); if (!driver) return -ENOMEM; rc = sclp_rw_init(); if (rc) { put_tty_driver(driver); return rc; } /* Allocate pages for output buffering */ INIT_LIST_HEAD(&sclp_tty_pages); for (i = 0; i < MAX_KMEM_PAGES; i++) { page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); if (page == NULL) { put_tty_driver(driver); return -ENOMEM; } list_add_tail((struct list_head *) page, &sclp_tty_pages); } INIT_LIST_HEAD(&sclp_tty_outqueue); spin_lock_init(&sclp_tty_lock); init_timer(&sclp_tty_timer); sclp_ttybuf = NULL; sclp_tty_buffer_count = 0; if (MACHINE_IS_VM) { /* * save 4 characters for the CPU number * written at start of each line by VM/CP */ sclp_tty_columns = 76; /* case input lines to lowercase */ sclp_tty_tolower = 1; } sclp_tty_chars_count = 0; rc = sclp_register(&sclp_input_event); if (rc) { put_tty_driver(driver); return rc; } tty_port_init(&sclp_port); driver->driver_name = "sclp_line"; driver->name = "sclp_line"; driver->major = TTY_MAJOR; driver->minor_start = 64; driver->type = TTY_DRIVER_TYPE_SYSTEM; driver->subtype = SYSTEM_TYPE_TTY; driver->init_termios = tty_std_termios; driver->init_termios.c_iflag = IGNBRK | IGNPAR; driver->init_termios.c_oflag = ONLCR | XTABS; driver->init_termios.c_lflag = ISIG | ECHO; driver->flags = TTY_DRIVER_REAL_RAW; tty_set_operations(driver, &sclp_ops); tty_port_link_device(&sclp_port, driver, 0); rc = tty_register_driver(driver); if (rc) { put_tty_driver(driver); tty_port_destroy(&sclp_port); return rc; } sclp_tty_driver = driver; return 0; } module_init(sclp_tty_init);