| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | *  drivers/s390/char/sclp_tty.c | 
|  | 3 | *    SCLP line mode terminal driver. | 
|  | 4 | * | 
|  | 5 | *  S390 version | 
|  | 6 | *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation | 
|  | 7 | *    Author(s): Martin Peschke <mpeschke@de.ibm.com> | 
|  | 8 | *		 Martin Schwidefsky <schwidefsky@de.ibm.com> | 
|  | 9 | */ | 
|  | 10 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 11 | #include <linux/module.h> | 
|  | 12 | #include <linux/kmod.h> | 
|  | 13 | #include <linux/tty.h> | 
|  | 14 | #include <linux/tty_driver.h> | 
| Alan Cox | 33f0f88 | 2006-01-09 20:54:13 -0800 | [diff] [blame] | 15 | #include <linux/tty_flip.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 16 | #include <linux/slab.h> | 
|  | 17 | #include <linux/err.h> | 
|  | 18 | #include <linux/init.h> | 
|  | 19 | #include <linux/interrupt.h> | 
|  | 20 | #include <asm/uaccess.h> | 
|  | 21 |  | 
|  | 22 | #include "ctrlchar.h" | 
|  | 23 | #include "sclp.h" | 
|  | 24 | #include "sclp_rw.h" | 
|  | 25 | #include "sclp_tty.h" | 
|  | 26 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 27 | /* | 
|  | 28 | * size of a buffer that collects single characters coming in | 
|  | 29 | * via sclp_tty_put_char() | 
|  | 30 | */ | 
|  | 31 | #define SCLP_TTY_BUF_SIZE 512 | 
|  | 32 |  | 
|  | 33 | /* | 
|  | 34 | * There is exactly one SCLP terminal, so we can keep things simple | 
|  | 35 | * and allocate all variables statically. | 
|  | 36 | */ | 
|  | 37 |  | 
|  | 38 | /* Lock to guard over changes to global variables. */ | 
|  | 39 | static spinlock_t sclp_tty_lock; | 
|  | 40 | /* List of free pages that can be used for console output buffering. */ | 
|  | 41 | static struct list_head sclp_tty_pages; | 
|  | 42 | /* List of full struct sclp_buffer structures ready for output. */ | 
|  | 43 | static struct list_head sclp_tty_outqueue; | 
|  | 44 | /* Counter how many buffers are emitted. */ | 
|  | 45 | static int sclp_tty_buffer_count; | 
|  | 46 | /* Pointer to current console buffer. */ | 
|  | 47 | static struct sclp_buffer *sclp_ttybuf; | 
|  | 48 | /* Timer for delayed output of console messages. */ | 
|  | 49 | static struct timer_list sclp_tty_timer; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 50 |  | 
|  | 51 | static struct tty_struct *sclp_tty; | 
|  | 52 | static unsigned char sclp_tty_chars[SCLP_TTY_BUF_SIZE]; | 
|  | 53 | static unsigned short int sclp_tty_chars_count; | 
|  | 54 |  | 
|  | 55 | struct tty_driver *sclp_tty_driver; | 
|  | 56 |  | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 57 | static int sclp_tty_tolower; | 
|  | 58 | static int sclp_tty_columns = 80; | 
|  | 59 |  | 
|  | 60 | #define SPACES_PER_TAB 8 | 
|  | 61 | #define CASE_DELIMITER 0x6c /* to separate upper and lower case (% in EBCDIC) */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 62 |  | 
|  | 63 | /* This routine is called whenever we try to open a SCLP terminal. */ | 
|  | 64 | static int | 
|  | 65 | sclp_tty_open(struct tty_struct *tty, struct file *filp) | 
|  | 66 | { | 
|  | 67 | sclp_tty = tty; | 
|  | 68 | tty->driver_data = NULL; | 
|  | 69 | tty->low_latency = 0; | 
|  | 70 | return 0; | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | /* This routine is called when the SCLP terminal is closed. */ | 
|  | 74 | static void | 
|  | 75 | sclp_tty_close(struct tty_struct *tty, struct file *filp) | 
|  | 76 | { | 
|  | 77 | if (tty->count > 1) | 
|  | 78 | return; | 
|  | 79 | sclp_tty = NULL; | 
|  | 80 | } | 
|  | 81 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 82 | /* | 
|  | 83 | * This routine returns the numbers of characters the tty driver | 
|  | 84 | * will accept for queuing to be written.  This number is subject | 
|  | 85 | * to change as output buffers get emptied, or if the output flow | 
|  | 86 | * control is acted. This is not an exact number because not every | 
|  | 87 | * character needs the same space in the sccb. The worst case is | 
|  | 88 | * a string of newlines. Every newlines creates a new mto which | 
|  | 89 | * needs 8 bytes. | 
|  | 90 | */ | 
|  | 91 | static int | 
|  | 92 | sclp_tty_write_room (struct tty_struct *tty) | 
|  | 93 | { | 
|  | 94 | unsigned long flags; | 
|  | 95 | struct list_head *l; | 
|  | 96 | int count; | 
|  | 97 |  | 
|  | 98 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 99 | count = 0; | 
|  | 100 | if (sclp_ttybuf != NULL) | 
|  | 101 | count = sclp_buffer_space(sclp_ttybuf) / sizeof(struct mto); | 
|  | 102 | list_for_each(l, &sclp_tty_pages) | 
|  | 103 | count += NR_EMPTY_MTO_PER_SCCB; | 
|  | 104 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
|  | 105 | return count; | 
|  | 106 | } | 
|  | 107 |  | 
|  | 108 | static void | 
|  | 109 | sclp_ttybuf_callback(struct sclp_buffer *buffer, int rc) | 
|  | 110 | { | 
|  | 111 | unsigned long flags; | 
|  | 112 | void *page; | 
|  | 113 |  | 
|  | 114 | do { | 
|  | 115 | page = sclp_unmake_buffer(buffer); | 
|  | 116 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 117 | /* Remove buffer from outqueue */ | 
|  | 118 | list_del(&buffer->list); | 
|  | 119 | sclp_tty_buffer_count--; | 
|  | 120 | list_add_tail((struct list_head *) page, &sclp_tty_pages); | 
|  | 121 | /* Check if there is a pending buffer on the out queue. */ | 
|  | 122 | buffer = NULL; | 
|  | 123 | if (!list_empty(&sclp_tty_outqueue)) | 
|  | 124 | buffer = list_entry(sclp_tty_outqueue.next, | 
|  | 125 | struct sclp_buffer, list); | 
|  | 126 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
|  | 127 | } while (buffer && sclp_emit_buffer(buffer, sclp_ttybuf_callback)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 128 | /* check if the tty needs a wake up call */ | 
|  | 129 | if (sclp_tty != NULL) { | 
|  | 130 | tty_wakeup(sclp_tty); | 
|  | 131 | } | 
|  | 132 | } | 
|  | 133 |  | 
|  | 134 | static inline void | 
|  | 135 | __sclp_ttybuf_emit(struct sclp_buffer *buffer) | 
|  | 136 | { | 
|  | 137 | unsigned long flags; | 
|  | 138 | int count; | 
|  | 139 | int rc; | 
|  | 140 |  | 
|  | 141 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 142 | list_add_tail(&buffer->list, &sclp_tty_outqueue); | 
|  | 143 | count = sclp_tty_buffer_count++; | 
|  | 144 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
|  | 145 | if (count) | 
|  | 146 | return; | 
|  | 147 | rc = sclp_emit_buffer(buffer, sclp_ttybuf_callback); | 
|  | 148 | if (rc) | 
|  | 149 | sclp_ttybuf_callback(buffer, rc); | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 | /* | 
|  | 153 | * When this routine is called from the timer then we flush the | 
|  | 154 | * temporary write buffer. | 
|  | 155 | */ | 
|  | 156 | static void | 
|  | 157 | sclp_tty_timeout(unsigned long data) | 
|  | 158 | { | 
|  | 159 | unsigned long flags; | 
|  | 160 | struct sclp_buffer *buf; | 
|  | 161 |  | 
|  | 162 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 163 | buf = sclp_ttybuf; | 
|  | 164 | sclp_ttybuf = NULL; | 
|  | 165 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
|  | 166 |  | 
|  | 167 | if (buf != NULL) { | 
|  | 168 | __sclp_ttybuf_emit(buf); | 
|  | 169 | } | 
|  | 170 | } | 
|  | 171 |  | 
|  | 172 | /* | 
|  | 173 | * Write a string to the sclp tty. | 
|  | 174 | */ | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 175 | static int sclp_tty_write_string(const unsigned char *str, int count, int may_fail) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 176 | { | 
|  | 177 | unsigned long flags; | 
|  | 178 | void *page; | 
|  | 179 | int written; | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 180 | int overall_written; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 181 | struct sclp_buffer *buf; | 
|  | 182 |  | 
|  | 183 | if (count <= 0) | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 184 | return 0; | 
|  | 185 | overall_written = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 186 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 187 | do { | 
|  | 188 | /* Create a sclp output buffer if none exists yet */ | 
|  | 189 | if (sclp_ttybuf == NULL) { | 
|  | 190 | while (list_empty(&sclp_tty_pages)) { | 
|  | 191 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 192 | if (may_fail) | 
|  | 193 | goto out; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 194 | else | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 195 | sclp_sync_wait(); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 196 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 197 | } | 
|  | 198 | page = sclp_tty_pages.next; | 
|  | 199 | list_del((struct list_head *) page); | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 200 | sclp_ttybuf = sclp_make_buffer(page, sclp_tty_columns, | 
|  | 201 | SPACES_PER_TAB); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 202 | } | 
|  | 203 | /* try to write the string to the current output buffer */ | 
|  | 204 | written = sclp_write(sclp_ttybuf, str, count); | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 205 | overall_written += written; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 206 | if (written == count) | 
|  | 207 | break; | 
|  | 208 | /* | 
|  | 209 | * Not all characters could be written to the current | 
|  | 210 | * output buffer. Emit the buffer, create a new buffer | 
|  | 211 | * and then output the rest of the string. | 
|  | 212 | */ | 
|  | 213 | buf = sclp_ttybuf; | 
|  | 214 | sclp_ttybuf = NULL; | 
|  | 215 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
|  | 216 | __sclp_ttybuf_emit(buf); | 
|  | 217 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 218 | str += written; | 
|  | 219 | count -= written; | 
|  | 220 | } while (count > 0); | 
|  | 221 | /* Setup timer to output current console buffer after 1/10 second */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 222 | if (sclp_ttybuf && sclp_chars_in_buffer(sclp_ttybuf) && | 
|  | 223 | !timer_pending(&sclp_tty_timer)) { | 
|  | 224 | init_timer(&sclp_tty_timer); | 
|  | 225 | sclp_tty_timer.function = sclp_tty_timeout; | 
|  | 226 | sclp_tty_timer.data = 0UL; | 
|  | 227 | sclp_tty_timer.expires = jiffies + HZ/10; | 
|  | 228 | add_timer(&sclp_tty_timer); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 229 | } | 
|  | 230 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 231 | out: | 
|  | 232 | return overall_written; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 233 | } | 
|  | 234 |  | 
|  | 235 | /* | 
|  | 236 | * This routine is called by the kernel to write a series of characters to the | 
|  | 237 | * tty device. The characters may come from user space or kernel space. This | 
|  | 238 | * routine will return the number of characters actually accepted for writing. | 
|  | 239 | */ | 
|  | 240 | static int | 
|  | 241 | sclp_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) | 
|  | 242 | { | 
|  | 243 | if (sclp_tty_chars_count > 0) { | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 244 | sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 245 | sclp_tty_chars_count = 0; | 
|  | 246 | } | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 247 | return sclp_tty_write_string(buf, count, 1); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 248 | } | 
|  | 249 |  | 
|  | 250 | /* | 
|  | 251 | * This routine is called by the kernel to write a single character to the tty | 
|  | 252 | * device. If the kernel uses this routine, it must call the flush_chars() | 
|  | 253 | * routine (if defined) when it is done stuffing characters into the driver. | 
|  | 254 | * | 
|  | 255 | * Characters provided to sclp_tty_put_char() are buffered by the SCLP driver. | 
|  | 256 | * If the given character is a '\n' the contents of the SCLP write buffer | 
|  | 257 | * - including previous characters from sclp_tty_put_char() and strings from | 
|  | 258 | * sclp_write() without final '\n' - will be written. | 
|  | 259 | */ | 
| Alan Cox | 9e7c9a1 | 2008-04-30 00:54:00 -0700 | [diff] [blame] | 260 | static int | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 261 | sclp_tty_put_char(struct tty_struct *tty, unsigned char ch) | 
|  | 262 | { | 
|  | 263 | sclp_tty_chars[sclp_tty_chars_count++] = ch; | 
|  | 264 | if (ch == '\n' || sclp_tty_chars_count >= SCLP_TTY_BUF_SIZE) { | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 265 | sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 266 | sclp_tty_chars_count = 0; | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 267 | } | 
|  | 268 | return 1; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 269 | } | 
|  | 270 |  | 
|  | 271 | /* | 
|  | 272 | * This routine is called by the kernel after it has written a series of | 
|  | 273 | * characters to the tty device using put_char(). | 
|  | 274 | */ | 
|  | 275 | static void | 
|  | 276 | sclp_tty_flush_chars(struct tty_struct *tty) | 
|  | 277 | { | 
|  | 278 | if (sclp_tty_chars_count > 0) { | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 279 | sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 280 | sclp_tty_chars_count = 0; | 
|  | 281 | } | 
|  | 282 | } | 
|  | 283 |  | 
|  | 284 | /* | 
|  | 285 | * This routine returns the number of characters in the write buffer of the | 
|  | 286 | * SCLP driver. The provided number includes all characters that are stored | 
|  | 287 | * in the SCCB (will be written next time the SCLP is not busy) as well as | 
|  | 288 | * characters in the write buffer (will not be written as long as there is a | 
|  | 289 | * final line feed missing). | 
|  | 290 | */ | 
|  | 291 | static int | 
|  | 292 | sclp_tty_chars_in_buffer(struct tty_struct *tty) | 
|  | 293 | { | 
|  | 294 | unsigned long flags; | 
|  | 295 | struct list_head *l; | 
|  | 296 | struct sclp_buffer *t; | 
|  | 297 | int count; | 
|  | 298 |  | 
|  | 299 | spin_lock_irqsave(&sclp_tty_lock, flags); | 
|  | 300 | count = 0; | 
|  | 301 | if (sclp_ttybuf != NULL) | 
|  | 302 | count = sclp_chars_in_buffer(sclp_ttybuf); | 
|  | 303 | list_for_each(l, &sclp_tty_outqueue) { | 
|  | 304 | t = list_entry(l, struct sclp_buffer, list); | 
|  | 305 | count += sclp_chars_in_buffer(t); | 
|  | 306 | } | 
|  | 307 | spin_unlock_irqrestore(&sclp_tty_lock, flags); | 
|  | 308 | return count; | 
|  | 309 | } | 
|  | 310 |  | 
|  | 311 | /* | 
|  | 312 | * removes all content from buffers of low level driver | 
|  | 313 | */ | 
|  | 314 | static void | 
|  | 315 | sclp_tty_flush_buffer(struct tty_struct *tty) | 
|  | 316 | { | 
|  | 317 | if (sclp_tty_chars_count > 0) { | 
| Heiko Carstens | 5e34599 | 2008-07-14 09:59:46 +0200 | [diff] [blame] | 318 | sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 319 | sclp_tty_chars_count = 0; | 
|  | 320 | } | 
|  | 321 | } | 
|  | 322 |  | 
|  | 323 | /* | 
|  | 324 | * push input to tty | 
|  | 325 | */ | 
|  | 326 | static void | 
|  | 327 | sclp_tty_input(unsigned char* buf, unsigned int count) | 
|  | 328 | { | 
|  | 329 | unsigned int cchar; | 
|  | 330 |  | 
|  | 331 | /* | 
|  | 332 | * If this tty driver is currently closed | 
|  | 333 | * then throw the received input away. | 
|  | 334 | */ | 
|  | 335 | if (sclp_tty == NULL) | 
|  | 336 | return; | 
|  | 337 | cchar = ctrlchar_handle(buf, count, sclp_tty); | 
|  | 338 | switch (cchar & CTRLCHAR_MASK) { | 
|  | 339 | case CTRLCHAR_SYSRQ: | 
|  | 340 | break; | 
|  | 341 | case CTRLCHAR_CTRL: | 
| Alan Cox | 33f0f88 | 2006-01-09 20:54:13 -0800 | [diff] [blame] | 342 | tty_insert_flip_char(sclp_tty, cchar, TTY_NORMAL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 343 | tty_flip_buffer_push(sclp_tty); | 
|  | 344 | break; | 
|  | 345 | case CTRLCHAR_NONE: | 
|  | 346 | /* send (normal) input to line discipline */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 347 | if (count < 2 || | 
| Alan Cox | 33f0f88 | 2006-01-09 20:54:13 -0800 | [diff] [blame] | 348 | (strncmp((const char *) buf + count - 2, "^n", 2) && | 
|  | 349 | strncmp((const char *) buf + count - 2, "\252n", 2))) { | 
|  | 350 | /* add the auto \n */ | 
|  | 351 | tty_insert_flip_string(sclp_tty, buf, count); | 
|  | 352 | tty_insert_flip_char(sclp_tty, '\n', TTY_NORMAL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 353 | } else | 
| Alan Cox | 33f0f88 | 2006-01-09 20:54:13 -0800 | [diff] [blame] | 354 | tty_insert_flip_string(sclp_tty, buf, count - 2); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 355 | tty_flip_buffer_push(sclp_tty); | 
|  | 356 | break; | 
|  | 357 | } | 
|  | 358 | } | 
|  | 359 |  | 
|  | 360 | /* | 
|  | 361 | * get a EBCDIC string in upper/lower case, | 
|  | 362 | * find out characters in lower/upper case separated by a special character, | 
|  | 363 | * modifiy original string, | 
|  | 364 | * returns length of resulting string | 
|  | 365 | */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 366 | static int sclp_switch_cases(unsigned char *buf, int count) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 367 | { | 
|  | 368 | unsigned char *ip, *op; | 
|  | 369 | int toggle; | 
|  | 370 |  | 
|  | 371 | /* initially changing case is off */ | 
|  | 372 | toggle = 0; | 
|  | 373 | ip = op = buf; | 
|  | 374 | while (count-- > 0) { | 
|  | 375 | /* compare with special character */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 376 | if (*ip == CASE_DELIMITER) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 377 | /* followed by another special character? */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 378 | if (count && ip[1] == CASE_DELIMITER) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 379 | /* | 
|  | 380 | * ... then put a single copy of the special | 
|  | 381 | * character to the output string | 
|  | 382 | */ | 
|  | 383 | *op++ = *ip++; | 
|  | 384 | count--; | 
|  | 385 | } else | 
|  | 386 | /* | 
|  | 387 | * ... special character follower by a normal | 
|  | 388 | * character toggles the case change behaviour | 
|  | 389 | */ | 
|  | 390 | toggle = ~toggle; | 
|  | 391 | /* skip special character */ | 
|  | 392 | ip++; | 
|  | 393 | } else | 
|  | 394 | /* not the special character */ | 
|  | 395 | if (toggle) | 
|  | 396 | /* but case switching is on */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 397 | if (sclp_tty_tolower) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 398 | /* switch to uppercase */ | 
|  | 399 | *op++ = _ebc_toupper[(int) *ip++]; | 
|  | 400 | else | 
|  | 401 | /* switch to lowercase */ | 
|  | 402 | *op++ = _ebc_tolower[(int) *ip++]; | 
|  | 403 | else | 
|  | 404 | /* no case switching, copy the character */ | 
|  | 405 | *op++ = *ip++; | 
|  | 406 | } | 
|  | 407 | /* return length of reformatted string. */ | 
|  | 408 | return op - buf; | 
|  | 409 | } | 
|  | 410 |  | 
|  | 411 | static void | 
|  | 412 | sclp_get_input(unsigned char *start, unsigned char *end) | 
|  | 413 | { | 
|  | 414 | int count; | 
|  | 415 |  | 
|  | 416 | count = end - start; | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 417 | if (sclp_tty_tolower) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 418 | EBC_TOLOWER(start, count); | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 419 | count = sclp_switch_cases(start, count); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 420 | /* convert EBCDIC to ASCII (modify original input in SCCB) */ | 
|  | 421 | sclp_ebcasc_str(start, count); | 
|  | 422 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 423 | /* transfer input to high level driver */ | 
|  | 424 | sclp_tty_input(start, count); | 
|  | 425 | } | 
|  | 426 |  | 
|  | 427 | static inline struct gds_vector * | 
|  | 428 | find_gds_vector(struct gds_vector *start, struct gds_vector *end, u16 id) | 
|  | 429 | { | 
|  | 430 | struct gds_vector *vec; | 
|  | 431 |  | 
|  | 432 | for (vec = start; vec < end; vec = (void *) vec + vec->length) | 
|  | 433 | if (vec->gds_id == id) | 
|  | 434 | return vec; | 
|  | 435 | return NULL; | 
|  | 436 | } | 
|  | 437 |  | 
|  | 438 | static inline struct gds_subvector * | 
|  | 439 | find_gds_subvector(struct gds_subvector *start, | 
|  | 440 | struct gds_subvector *end, u8 key) | 
|  | 441 | { | 
|  | 442 | struct gds_subvector *subvec; | 
|  | 443 |  | 
|  | 444 | for (subvec = start; subvec < end; | 
|  | 445 | subvec = (void *) subvec + subvec->length) | 
|  | 446 | if (subvec->key == key) | 
|  | 447 | return subvec; | 
|  | 448 | return NULL; | 
|  | 449 | } | 
|  | 450 |  | 
|  | 451 | static inline void | 
|  | 452 | sclp_eval_selfdeftextmsg(struct gds_subvector *start, | 
|  | 453 | struct gds_subvector *end) | 
|  | 454 | { | 
|  | 455 | struct gds_subvector *subvec; | 
|  | 456 |  | 
|  | 457 | subvec = start; | 
|  | 458 | while (subvec < end) { | 
|  | 459 | subvec = find_gds_subvector(subvec, end, 0x30); | 
|  | 460 | if (!subvec) | 
|  | 461 | break; | 
|  | 462 | sclp_get_input((unsigned char *)(subvec + 1), | 
|  | 463 | (unsigned char *) subvec + subvec->length); | 
|  | 464 | subvec = (void *) subvec + subvec->length; | 
|  | 465 | } | 
|  | 466 | } | 
|  | 467 |  | 
|  | 468 | static inline void | 
|  | 469 | sclp_eval_textcmd(struct gds_subvector *start, | 
|  | 470 | struct gds_subvector *end) | 
|  | 471 | { | 
|  | 472 | struct gds_subvector *subvec; | 
|  | 473 |  | 
|  | 474 | subvec = start; | 
|  | 475 | while (subvec < end) { | 
|  | 476 | subvec = find_gds_subvector(subvec, end, | 
| Stefan Haberland | 6d4740c | 2007-04-27 16:01:53 +0200 | [diff] [blame] | 477 | GDS_KEY_SELFDEFTEXTMSG); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 478 | if (!subvec) | 
|  | 479 | break; | 
|  | 480 | sclp_eval_selfdeftextmsg((struct gds_subvector *)(subvec + 1), | 
|  | 481 | (void *)subvec + subvec->length); | 
|  | 482 | subvec = (void *) subvec + subvec->length; | 
|  | 483 | } | 
|  | 484 | } | 
|  | 485 |  | 
|  | 486 | static inline void | 
|  | 487 | sclp_eval_cpmsu(struct gds_vector *start, struct gds_vector *end) | 
|  | 488 | { | 
|  | 489 | struct gds_vector *vec; | 
|  | 490 |  | 
|  | 491 | vec = start; | 
|  | 492 | while (vec < end) { | 
| Stefan Haberland | 6d4740c | 2007-04-27 16:01:53 +0200 | [diff] [blame] | 493 | vec = find_gds_vector(vec, end, GDS_ID_TEXTCMD); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 494 | if (!vec) | 
|  | 495 | break; | 
|  | 496 | sclp_eval_textcmd((struct gds_subvector *)(vec + 1), | 
|  | 497 | (void *) vec + vec->length); | 
|  | 498 | vec = (void *) vec + vec->length; | 
|  | 499 | } | 
|  | 500 | } | 
|  | 501 |  | 
|  | 502 |  | 
|  | 503 | static inline void | 
|  | 504 | sclp_eval_mdsmu(struct gds_vector *start, void *end) | 
|  | 505 | { | 
|  | 506 | struct gds_vector *vec; | 
|  | 507 |  | 
|  | 508 | vec = find_gds_vector(start, end, GDS_ID_CPMSU); | 
|  | 509 | if (vec) | 
|  | 510 | sclp_eval_cpmsu(vec + 1, (void *) vec + vec->length); | 
|  | 511 | } | 
|  | 512 |  | 
|  | 513 | static void | 
|  | 514 | sclp_tty_receiver(struct evbuf_header *evbuf) | 
|  | 515 | { | 
|  | 516 | struct gds_vector *start, *end, *vec; | 
|  | 517 |  | 
|  | 518 | start = (struct gds_vector *)(evbuf + 1); | 
|  | 519 | end = (void *) evbuf + evbuf->length; | 
|  | 520 | vec = find_gds_vector(start, end, GDS_ID_MDSMU); | 
|  | 521 | if (vec) | 
|  | 522 | sclp_eval_mdsmu(vec + 1, (void *) vec + vec->length); | 
|  | 523 | } | 
|  | 524 |  | 
|  | 525 | static void | 
|  | 526 | sclp_tty_state_change(struct sclp_register *reg) | 
|  | 527 | { | 
|  | 528 | } | 
|  | 529 |  | 
|  | 530 | static struct sclp_register sclp_input_event = | 
|  | 531 | { | 
| Stefan Haberland | 6d4740c | 2007-04-27 16:01:53 +0200 | [diff] [blame] | 532 | .receive_mask = EVTYP_OPCMD_MASK | EVTYP_PMSGCMD_MASK, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 533 | .state_change_fn = sclp_tty_state_change, | 
|  | 534 | .receiver_fn = sclp_tty_receiver | 
|  | 535 | }; | 
|  | 536 |  | 
| Jeff Dike | b68e31d | 2006-10-02 02:17:18 -0700 | [diff] [blame] | 537 | static const struct tty_operations sclp_ops = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 538 | .open = sclp_tty_open, | 
|  | 539 | .close = sclp_tty_close, | 
|  | 540 | .write = sclp_tty_write, | 
|  | 541 | .put_char = sclp_tty_put_char, | 
|  | 542 | .flush_chars = sclp_tty_flush_chars, | 
|  | 543 | .write_room = sclp_tty_write_room, | 
|  | 544 | .chars_in_buffer = sclp_tty_chars_in_buffer, | 
|  | 545 | .flush_buffer = sclp_tty_flush_buffer, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 546 | }; | 
|  | 547 |  | 
| Heiko Carstens | 2b67fc4 | 2007-02-05 21:16:47 +0100 | [diff] [blame] | 548 | static int __init | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 549 | sclp_tty_init(void) | 
|  | 550 | { | 
|  | 551 | struct tty_driver *driver; | 
|  | 552 | void *page; | 
|  | 553 | int i; | 
|  | 554 | int rc; | 
|  | 555 |  | 
|  | 556 | if (!CONSOLE_IS_SCLP) | 
|  | 557 | return 0; | 
|  | 558 | driver = alloc_tty_driver(1); | 
|  | 559 | if (!driver) | 
|  | 560 | return -ENOMEM; | 
|  | 561 |  | 
|  | 562 | rc = sclp_rw_init(); | 
|  | 563 | if (rc) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 564 | put_tty_driver(driver); | 
|  | 565 | return rc; | 
|  | 566 | } | 
|  | 567 | /* Allocate pages for output buffering */ | 
|  | 568 | INIT_LIST_HEAD(&sclp_tty_pages); | 
|  | 569 | for (i = 0; i < MAX_KMEM_PAGES; i++) { | 
|  | 570 | page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); | 
|  | 571 | if (page == NULL) { | 
|  | 572 | put_tty_driver(driver); | 
|  | 573 | return -ENOMEM; | 
|  | 574 | } | 
|  | 575 | list_add_tail((struct list_head *) page, &sclp_tty_pages); | 
|  | 576 | } | 
|  | 577 | INIT_LIST_HEAD(&sclp_tty_outqueue); | 
|  | 578 | spin_lock_init(&sclp_tty_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 579 | init_timer(&sclp_tty_timer); | 
|  | 580 | sclp_ttybuf = NULL; | 
|  | 581 | sclp_tty_buffer_count = 0; | 
|  | 582 | if (MACHINE_IS_VM) { | 
|  | 583 | /* | 
|  | 584 | * save 4 characters for the CPU number | 
|  | 585 | * written at start of each line by VM/CP | 
|  | 586 | */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 587 | sclp_tty_columns = 76; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 588 | /* case input lines to lowercase */ | 
| Heiko Carstens | 095761d | 2008-07-14 09:59:45 +0200 | [diff] [blame] | 589 | sclp_tty_tolower = 1; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 590 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 591 | sclp_tty_chars_count = 0; | 
|  | 592 | sclp_tty = NULL; | 
|  | 593 |  | 
|  | 594 | rc = sclp_register(&sclp_input_event); | 
|  | 595 | if (rc) { | 
|  | 596 | put_tty_driver(driver); | 
|  | 597 | return rc; | 
|  | 598 | } | 
|  | 599 |  | 
|  | 600 | driver->owner = THIS_MODULE; | 
|  | 601 | driver->driver_name = "sclp_line"; | 
|  | 602 | driver->name = "sclp_line"; | 
|  | 603 | driver->major = TTY_MAJOR; | 
|  | 604 | driver->minor_start = 64; | 
|  | 605 | driver->type = TTY_DRIVER_TYPE_SYSTEM; | 
|  | 606 | driver->subtype = SYSTEM_TYPE_TTY; | 
|  | 607 | driver->init_termios = tty_std_termios; | 
|  | 608 | driver->init_termios.c_iflag = IGNBRK | IGNPAR; | 
|  | 609 | driver->init_termios.c_oflag = ONLCR | XTABS; | 
|  | 610 | driver->init_termios.c_lflag = ISIG | ECHO; | 
|  | 611 | driver->flags = TTY_DRIVER_REAL_RAW; | 
|  | 612 | tty_set_operations(driver, &sclp_ops); | 
|  | 613 | rc = tty_register_driver(driver); | 
|  | 614 | if (rc) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 615 | put_tty_driver(driver); | 
|  | 616 | return rc; | 
|  | 617 | } | 
|  | 618 | sclp_tty_driver = driver; | 
|  | 619 | return 0; | 
|  | 620 | } | 
|  | 621 | module_init(sclp_tty_init); |