diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/char/sclp_vt220.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/char/sclp_vt220.c')
-rw-r--r-- | drivers/s390/char/sclp_vt220.c | 785 |
1 files changed, 785 insertions, 0 deletions
diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c new file mode 100644 index 000000000000..06bd85824d7b --- /dev/null +++ b/drivers/s390/char/sclp_vt220.c | |||
@@ -0,0 +1,785 @@ | |||
1 | /* | ||
2 | * drivers/s390/char/sclp_vt220.c | ||
3 | * SCLP VT220 terminal driver. | ||
4 | * | ||
5 | * S390 version | ||
6 | * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation | ||
7 | * Author(s): Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> | ||
8 | */ | ||
9 | |||
10 | #include <linux/config.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/spinlock.h> | ||
13 | #include <linux/list.h> | ||
14 | #include <linux/wait.h> | ||
15 | #include <linux/timer.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/tty.h> | ||
18 | #include <linux/tty_driver.h> | ||
19 | #include <linux/sched.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/mm.h> | ||
22 | #include <linux/major.h> | ||
23 | #include <linux/console.h> | ||
24 | #include <linux/kdev_t.h> | ||
25 | #include <linux/bootmem.h> | ||
26 | #include <linux/interrupt.h> | ||
27 | #include <linux/init.h> | ||
28 | #include <asm/uaccess.h> | ||
29 | #include "sclp.h" | ||
30 | |||
31 | #define SCLP_VT220_PRINT_HEADER "sclp vt220 tty driver: " | ||
32 | #define SCLP_VT220_MAJOR TTY_MAJOR | ||
33 | #define SCLP_VT220_MINOR 65 | ||
34 | #define SCLP_VT220_DRIVER_NAME "sclp_vt220" | ||
35 | #define SCLP_VT220_DEVICE_NAME "ttysclp" | ||
36 | #define SCLP_VT220_CONSOLE_NAME "ttyS" | ||
37 | #define SCLP_VT220_CONSOLE_INDEX 1 /* console=ttyS1 */ | ||
38 | #define SCLP_VT220_BUF_SIZE 80 | ||
39 | |||
40 | /* Representation of a single write request */ | ||
41 | struct sclp_vt220_request { | ||
42 | struct list_head list; | ||
43 | struct sclp_req sclp_req; | ||
44 | int retry_count; | ||
45 | }; | ||
46 | |||
47 | /* VT220 SCCB */ | ||
48 | struct sclp_vt220_sccb { | ||
49 | struct sccb_header header; | ||
50 | struct evbuf_header evbuf; | ||
51 | }; | ||
52 | |||
53 | #define SCLP_VT220_MAX_CHARS_PER_BUFFER (PAGE_SIZE - \ | ||
54 | sizeof(struct sclp_vt220_request) - \ | ||
55 | sizeof(struct sclp_vt220_sccb)) | ||
56 | |||
57 | /* Structures and data needed to register tty driver */ | ||
58 | static struct tty_driver *sclp_vt220_driver; | ||
59 | |||
60 | /* The tty_struct that the kernel associated with us */ | ||
61 | static struct tty_struct *sclp_vt220_tty; | ||
62 | |||
63 | /* Lock to protect internal data from concurrent access */ | ||
64 | static spinlock_t sclp_vt220_lock; | ||
65 | |||
66 | /* List of empty pages to be used as write request buffers */ | ||
67 | static struct list_head sclp_vt220_empty; | ||
68 | |||
69 | /* List of pending requests */ | ||
70 | static struct list_head sclp_vt220_outqueue; | ||
71 | |||
72 | /* Number of requests in outqueue */ | ||
73 | static int sclp_vt220_outqueue_count; | ||
74 | |||
75 | /* Wait queue used to delay write requests while we've run out of buffers */ | ||
76 | static wait_queue_head_t sclp_vt220_waitq; | ||
77 | |||
78 | /* Timer used for delaying write requests to merge subsequent messages into | ||
79 | * a single buffer */ | ||
80 | static struct timer_list sclp_vt220_timer; | ||
81 | |||
82 | /* Pointer to current request buffer which has been partially filled but not | ||
83 | * yet sent */ | ||
84 | static struct sclp_vt220_request *sclp_vt220_current_request; | ||
85 | |||
86 | /* Number of characters in current request buffer */ | ||
87 | static int sclp_vt220_buffered_chars; | ||
88 | |||
89 | /* Flag indicating whether this driver has already been initialized */ | ||
90 | static int sclp_vt220_initialized = 0; | ||
91 | |||
92 | /* Flag indicating that sclp_vt220_current_request should really | ||
93 | * have been already queued but wasn't because the SCLP was processing | ||
94 | * another buffer */ | ||
95 | static int sclp_vt220_flush_later; | ||
96 | |||
97 | static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf); | ||
98 | static int __sclp_vt220_emit(struct sclp_vt220_request *request); | ||
99 | static void sclp_vt220_emit_current(void); | ||
100 | |||
101 | /* Registration structure for our interest in SCLP event buffers */ | ||
102 | static struct sclp_register sclp_vt220_register = { | ||
103 | .send_mask = EvTyp_VT220Msg_Mask, | ||
104 | .receive_mask = EvTyp_VT220Msg_Mask, | ||
105 | .state_change_fn = NULL, | ||
106 | .receiver_fn = sclp_vt220_receiver_fn | ||
107 | }; | ||
108 | |||
109 | |||
110 | /* | ||
111 | * Put provided request buffer back into queue and check emit pending | ||
112 | * buffers if necessary. | ||
113 | */ | ||
114 | static void | ||
115 | sclp_vt220_process_queue(struct sclp_vt220_request *request) | ||
116 | { | ||
117 | unsigned long flags; | ||
118 | void *page; | ||
119 | |||
120 | do { | ||
121 | /* Put buffer back to list of empty buffers */ | ||
122 | page = request->sclp_req.sccb; | ||
123 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
124 | /* Move request from outqueue to empty queue */ | ||
125 | list_del(&request->list); | ||
126 | sclp_vt220_outqueue_count--; | ||
127 | list_add_tail((struct list_head *) page, &sclp_vt220_empty); | ||
128 | /* Check if there is a pending buffer on the out queue. */ | ||
129 | request = NULL; | ||
130 | if (!list_empty(&sclp_vt220_outqueue)) | ||
131 | request = list_entry(sclp_vt220_outqueue.next, | ||
132 | struct sclp_vt220_request, list); | ||
133 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
134 | } while (request && __sclp_vt220_emit(request)); | ||
135 | if (request == NULL && sclp_vt220_flush_later) | ||
136 | sclp_vt220_emit_current(); | ||
137 | wake_up(&sclp_vt220_waitq); | ||
138 | /* Check if the tty needs a wake up call */ | ||
139 | if (sclp_vt220_tty != NULL) { | ||
140 | tty_wakeup(sclp_vt220_tty); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | #define SCLP_BUFFER_MAX_RETRY 1 | ||
145 | |||
146 | /* | ||
147 | * Callback through which the result of a write request is reported by the | ||
148 | * SCLP. | ||
149 | */ | ||
150 | static void | ||
151 | sclp_vt220_callback(struct sclp_req *request, void *data) | ||
152 | { | ||
153 | struct sclp_vt220_request *vt220_request; | ||
154 | struct sclp_vt220_sccb *sccb; | ||
155 | |||
156 | vt220_request = (struct sclp_vt220_request *) data; | ||
157 | if (request->status == SCLP_REQ_FAILED) { | ||
158 | sclp_vt220_process_queue(vt220_request); | ||
159 | return; | ||
160 | } | ||
161 | sccb = (struct sclp_vt220_sccb *) vt220_request->sclp_req.sccb; | ||
162 | |||
163 | /* Check SCLP response code and choose suitable action */ | ||
164 | switch (sccb->header.response_code) { | ||
165 | case 0x0020 : | ||
166 | break; | ||
167 | |||
168 | case 0x05f0: /* Target resource in improper state */ | ||
169 | break; | ||
170 | |||
171 | case 0x0340: /* Contained SCLP equipment check */ | ||
172 | if (++vt220_request->retry_count > SCLP_BUFFER_MAX_RETRY) | ||
173 | break; | ||
174 | /* Remove processed buffers and requeue rest */ | ||
175 | if (sclp_remove_processed((struct sccb_header *) sccb) > 0) { | ||
176 | /* Not all buffers were processed */ | ||
177 | sccb->header.response_code = 0x0000; | ||
178 | vt220_request->sclp_req.status = SCLP_REQ_FILLED; | ||
179 | if (sclp_add_request(request) == 0) | ||
180 | return; | ||
181 | } | ||
182 | break; | ||
183 | |||
184 | case 0x0040: /* SCLP equipment check */ | ||
185 | if (++vt220_request->retry_count > SCLP_BUFFER_MAX_RETRY) | ||
186 | break; | ||
187 | sccb->header.response_code = 0x0000; | ||
188 | vt220_request->sclp_req.status = SCLP_REQ_FILLED; | ||
189 | if (sclp_add_request(request) == 0) | ||
190 | return; | ||
191 | break; | ||
192 | |||
193 | default: | ||
194 | break; | ||
195 | } | ||
196 | sclp_vt220_process_queue(vt220_request); | ||
197 | } | ||
198 | |||
199 | /* | ||
200 | * Emit vt220 request buffer to SCLP. Return zero on success, non-zero | ||
201 | * otherwise. | ||
202 | */ | ||
203 | static int | ||
204 | __sclp_vt220_emit(struct sclp_vt220_request *request) | ||
205 | { | ||
206 | if (!(sclp_vt220_register.sclp_send_mask & EvTyp_VT220Msg_Mask)) { | ||
207 | request->sclp_req.status = SCLP_REQ_FAILED; | ||
208 | return -EIO; | ||
209 | } | ||
210 | request->sclp_req.command = SCLP_CMDW_WRITEDATA; | ||
211 | request->sclp_req.status = SCLP_REQ_FILLED; | ||
212 | request->sclp_req.callback = sclp_vt220_callback; | ||
213 | request->sclp_req.callback_data = (void *) request; | ||
214 | |||
215 | return sclp_add_request(&request->sclp_req); | ||
216 | } | ||
217 | |||
218 | /* | ||
219 | * Queue and emit given request. | ||
220 | */ | ||
221 | static void | ||
222 | sclp_vt220_emit(struct sclp_vt220_request *request) | ||
223 | { | ||
224 | unsigned long flags; | ||
225 | int count; | ||
226 | |||
227 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
228 | list_add_tail(&request->list, &sclp_vt220_outqueue); | ||
229 | count = sclp_vt220_outqueue_count++; | ||
230 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
231 | /* Emit only the first buffer immediately - callback takes care of | ||
232 | * the rest */ | ||
233 | if (count == 0 && __sclp_vt220_emit(request)) | ||
234 | sclp_vt220_process_queue(request); | ||
235 | } | ||
236 | |||
237 | /* | ||
238 | * Queue and emit current request. Return zero on success, non-zero otherwise. | ||
239 | */ | ||
240 | static void | ||
241 | sclp_vt220_emit_current(void) | ||
242 | { | ||
243 | unsigned long flags; | ||
244 | struct sclp_vt220_request *request; | ||
245 | struct sclp_vt220_sccb *sccb; | ||
246 | |||
247 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
248 | request = NULL; | ||
249 | if (sclp_vt220_current_request != NULL) { | ||
250 | sccb = (struct sclp_vt220_sccb *) | ||
251 | sclp_vt220_current_request->sclp_req.sccb; | ||
252 | /* Only emit buffers with content */ | ||
253 | if (sccb->header.length != sizeof(struct sclp_vt220_sccb)) { | ||
254 | request = sclp_vt220_current_request; | ||
255 | sclp_vt220_current_request = NULL; | ||
256 | if (timer_pending(&sclp_vt220_timer)) | ||
257 | del_timer(&sclp_vt220_timer); | ||
258 | } | ||
259 | sclp_vt220_flush_later = 0; | ||
260 | } | ||
261 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
262 | if (request != NULL) | ||
263 | sclp_vt220_emit(request); | ||
264 | } | ||
265 | |||
266 | #define SCLP_NORMAL_WRITE 0x00 | ||
267 | |||
268 | /* | ||
269 | * Helper function to initialize a page with the sclp request structure. | ||
270 | */ | ||
271 | static struct sclp_vt220_request * | ||
272 | sclp_vt220_initialize_page(void *page) | ||
273 | { | ||
274 | struct sclp_vt220_request *request; | ||
275 | struct sclp_vt220_sccb *sccb; | ||
276 | |||
277 | /* Place request structure at end of page */ | ||
278 | request = ((struct sclp_vt220_request *) | ||
279 | ((addr_t) page + PAGE_SIZE)) - 1; | ||
280 | request->retry_count = 0; | ||
281 | request->sclp_req.sccb = page; | ||
282 | /* SCCB goes at start of page */ | ||
283 | sccb = (struct sclp_vt220_sccb *) page; | ||
284 | memset((void *) sccb, 0, sizeof(struct sclp_vt220_sccb)); | ||
285 | sccb->header.length = sizeof(struct sclp_vt220_sccb); | ||
286 | sccb->header.function_code = SCLP_NORMAL_WRITE; | ||
287 | sccb->header.response_code = 0x0000; | ||
288 | sccb->evbuf.type = EvTyp_VT220Msg; | ||
289 | sccb->evbuf.length = sizeof(struct evbuf_header); | ||
290 | |||
291 | return request; | ||
292 | } | ||
293 | |||
294 | static inline unsigned int | ||
295 | sclp_vt220_space_left(struct sclp_vt220_request *request) | ||
296 | { | ||
297 | struct sclp_vt220_sccb *sccb; | ||
298 | sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb; | ||
299 | return PAGE_SIZE - sizeof(struct sclp_vt220_request) - | ||
300 | sccb->header.length; | ||
301 | } | ||
302 | |||
303 | static inline unsigned int | ||
304 | sclp_vt220_chars_stored(struct sclp_vt220_request *request) | ||
305 | { | ||
306 | struct sclp_vt220_sccb *sccb; | ||
307 | sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb; | ||
308 | return sccb->evbuf.length - sizeof(struct evbuf_header); | ||
309 | } | ||
310 | |||
311 | /* | ||
312 | * Add msg to buffer associated with request. Return the number of characters | ||
313 | * added. | ||
314 | */ | ||
315 | static int | ||
316 | sclp_vt220_add_msg(struct sclp_vt220_request *request, | ||
317 | const unsigned char *msg, int count, int convertlf) | ||
318 | { | ||
319 | struct sclp_vt220_sccb *sccb; | ||
320 | void *buffer; | ||
321 | unsigned char c; | ||
322 | int from; | ||
323 | int to; | ||
324 | |||
325 | if (count > sclp_vt220_space_left(request)) | ||
326 | count = sclp_vt220_space_left(request); | ||
327 | if (count <= 0) | ||
328 | return 0; | ||
329 | |||
330 | sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb; | ||
331 | buffer = (void *) ((addr_t) sccb + sccb->header.length); | ||
332 | |||
333 | if (convertlf) { | ||
334 | /* Perform Linefeed conversion (0x0a -> 0x0a 0x0d)*/ | ||
335 | for (from=0, to=0; | ||
336 | (from < count) && (to < sclp_vt220_space_left(request)); | ||
337 | from++) { | ||
338 | /* Retrieve character */ | ||
339 | c = msg[from]; | ||
340 | /* Perform conversion */ | ||
341 | if (c == 0x0a) { | ||
342 | if (to + 1 < sclp_vt220_space_left(request)) { | ||
343 | ((unsigned char *) buffer)[to++] = c; | ||
344 | ((unsigned char *) buffer)[to++] = 0x0d; | ||
345 | } else | ||
346 | break; | ||
347 | |||
348 | } else | ||
349 | ((unsigned char *) buffer)[to++] = c; | ||
350 | } | ||
351 | sccb->header.length += to; | ||
352 | sccb->evbuf.length += to; | ||
353 | return from; | ||
354 | } else { | ||
355 | memcpy(buffer, (const void *) msg, count); | ||
356 | sccb->header.length += count; | ||
357 | sccb->evbuf.length += count; | ||
358 | return count; | ||
359 | } | ||
360 | } | ||
361 | |||
362 | /* | ||
363 | * Emit buffer after having waited long enough for more data to arrive. | ||
364 | */ | ||
365 | static void | ||
366 | sclp_vt220_timeout(unsigned long data) | ||
367 | { | ||
368 | sclp_vt220_emit_current(); | ||
369 | } | ||
370 | |||
371 | #define BUFFER_MAX_DELAY HZ/2 | ||
372 | |||
373 | /* | ||
374 | * Internal implementation of the write function. Write COUNT bytes of data | ||
375 | * from memory at BUF | ||
376 | * to the SCLP interface. In case that the data does not fit into the current | ||
377 | * write buffer, emit the current one and allocate a new one. If there are no | ||
378 | * more empty buffers available, wait until one gets emptied. If DO_SCHEDULE | ||
379 | * is non-zero, the buffer will be scheduled for emitting after a timeout - | ||
380 | * otherwise the user has to explicitly call the flush function. | ||
381 | * A non-zero CONVERTLF parameter indicates that 0x0a characters in the message | ||
382 | * buffer should be converted to 0x0a 0x0d. After completion, return the number | ||
383 | * of bytes written. | ||
384 | */ | ||
385 | static int | ||
386 | __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, | ||
387 | int convertlf) | ||
388 | { | ||
389 | unsigned long flags; | ||
390 | void *page; | ||
391 | int written; | ||
392 | int overall_written; | ||
393 | |||
394 | if (count <= 0) | ||
395 | return 0; | ||
396 | overall_written = 0; | ||
397 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
398 | do { | ||
399 | /* Create a sclp output buffer if none exists yet */ | ||
400 | if (sclp_vt220_current_request == NULL) { | ||
401 | while (list_empty(&sclp_vt220_empty)) { | ||
402 | spin_unlock_irqrestore(&sclp_vt220_lock, | ||
403 | flags); | ||
404 | if (in_interrupt()) | ||
405 | sclp_sync_wait(); | ||
406 | else | ||
407 | wait_event(sclp_vt220_waitq, | ||
408 | !list_empty(&sclp_vt220_empty)); | ||
409 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
410 | } | ||
411 | page = (void *) sclp_vt220_empty.next; | ||
412 | list_del((struct list_head *) page); | ||
413 | sclp_vt220_current_request = | ||
414 | sclp_vt220_initialize_page(page); | ||
415 | } | ||
416 | /* Try to write the string to the current request buffer */ | ||
417 | written = sclp_vt220_add_msg(sclp_vt220_current_request, | ||
418 | buf, count, convertlf); | ||
419 | overall_written += written; | ||
420 | if (written == count) | ||
421 | break; | ||
422 | /* | ||
423 | * Not all characters could be written to the current | ||
424 | * output buffer. Emit the buffer, create a new buffer | ||
425 | * and then output the rest of the string. | ||
426 | */ | ||
427 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
428 | sclp_vt220_emit_current(); | ||
429 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
430 | buf += written; | ||
431 | count -= written; | ||
432 | } while (count > 0); | ||
433 | /* Setup timer to output current console buffer after some time */ | ||
434 | if (sclp_vt220_current_request != NULL && | ||
435 | !timer_pending(&sclp_vt220_timer) && do_schedule) { | ||
436 | sclp_vt220_timer.function = sclp_vt220_timeout; | ||
437 | sclp_vt220_timer.data = 0UL; | ||
438 | sclp_vt220_timer.expires = jiffies + BUFFER_MAX_DELAY; | ||
439 | add_timer(&sclp_vt220_timer); | ||
440 | } | ||
441 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
442 | return overall_written; | ||
443 | } | ||
444 | |||
445 | /* | ||
446 | * This routine is called by the kernel to write a series of | ||
447 | * characters to the tty device. The characters may come from | ||
448 | * user space or kernel space. This routine will return the | ||
449 | * number of characters actually accepted for writing. | ||
450 | */ | ||
451 | static int | ||
452 | sclp_vt220_write(struct tty_struct *tty, const unsigned char *buf, int count) | ||
453 | { | ||
454 | return __sclp_vt220_write(buf, count, 1, 0); | ||
455 | } | ||
456 | |||
457 | #define SCLP_VT220_SESSION_ENDED 0x01 | ||
458 | #define SCLP_VT220_SESSION_STARTED 0x80 | ||
459 | #define SCLP_VT220_SESSION_DATA 0x00 | ||
460 | |||
461 | /* | ||
462 | * Called by the SCLP to report incoming event buffers. | ||
463 | */ | ||
464 | static void | ||
465 | sclp_vt220_receiver_fn(struct evbuf_header *evbuf) | ||
466 | { | ||
467 | char *buffer; | ||
468 | unsigned int count; | ||
469 | |||
470 | /* Ignore input if device is not open */ | ||
471 | if (sclp_vt220_tty == NULL) | ||
472 | return; | ||
473 | |||
474 | buffer = (char *) ((addr_t) evbuf + sizeof(struct evbuf_header)); | ||
475 | count = evbuf->length - sizeof(struct evbuf_header); | ||
476 | |||
477 | switch (*buffer) { | ||
478 | case SCLP_VT220_SESSION_ENDED: | ||
479 | case SCLP_VT220_SESSION_STARTED: | ||
480 | break; | ||
481 | case SCLP_VT220_SESSION_DATA: | ||
482 | /* Send input to line discipline */ | ||
483 | buffer++; | ||
484 | count--; | ||
485 | /* Prevent buffer overrun by discarding input. Note that | ||
486 | * because buffer_push works asynchronously, we cannot wait | ||
487 | * for the buffer to be emptied. */ | ||
488 | if (count + sclp_vt220_tty->flip.count > TTY_FLIPBUF_SIZE) | ||
489 | count = TTY_FLIPBUF_SIZE - sclp_vt220_tty->flip.count; | ||
490 | memcpy(sclp_vt220_tty->flip.char_buf_ptr, buffer, count); | ||
491 | memset(sclp_vt220_tty->flip.flag_buf_ptr, TTY_NORMAL, count); | ||
492 | sclp_vt220_tty->flip.char_buf_ptr += count; | ||
493 | sclp_vt220_tty->flip.flag_buf_ptr += count; | ||
494 | sclp_vt220_tty->flip.count += count; | ||
495 | tty_flip_buffer_push(sclp_vt220_tty); | ||
496 | break; | ||
497 | } | ||
498 | } | ||
499 | |||
500 | /* | ||
501 | * This routine is called when a particular tty device is opened. | ||
502 | */ | ||
503 | static int | ||
504 | sclp_vt220_open(struct tty_struct *tty, struct file *filp) | ||
505 | { | ||
506 | if (tty->count == 1) { | ||
507 | sclp_vt220_tty = tty; | ||
508 | tty->driver_data = kmalloc(SCLP_VT220_BUF_SIZE, GFP_KERNEL); | ||
509 | if (tty->driver_data == NULL) | ||
510 | return -ENOMEM; | ||
511 | tty->low_latency = 0; | ||
512 | } | ||
513 | return 0; | ||
514 | } | ||
515 | |||
516 | /* | ||
517 | * This routine is called when a particular tty device is closed. | ||
518 | */ | ||
519 | static void | ||
520 | sclp_vt220_close(struct tty_struct *tty, struct file *filp) | ||
521 | { | ||
522 | if (tty->count == 1) { | ||
523 | sclp_vt220_tty = NULL; | ||
524 | kfree(tty->driver_data); | ||
525 | tty->driver_data = NULL; | ||
526 | } | ||
527 | } | ||
528 | |||
529 | /* | ||
530 | * This routine is called by the kernel to write a single | ||
531 | * character to the tty device. If the kernel uses this routine, | ||
532 | * it must call the flush_chars() routine (if defined) when it is | ||
533 | * done stuffing characters into the driver. | ||
534 | * | ||
535 | * NOTE: include/linux/tty_driver.h specifies that a character should be | ||
536 | * ignored if there is no room in the queue. This driver implements a different | ||
537 | * semantic in that it will block when there is no more room left. | ||
538 | */ | ||
539 | static void | ||
540 | sclp_vt220_put_char(struct tty_struct *tty, unsigned char ch) | ||
541 | { | ||
542 | __sclp_vt220_write(&ch, 1, 0, 0); | ||
543 | } | ||
544 | |||
545 | /* | ||
546 | * This routine is called by the kernel after it has written a | ||
547 | * series of characters to the tty device using put_char(). | ||
548 | */ | ||
549 | static void | ||
550 | sclp_vt220_flush_chars(struct tty_struct *tty) | ||
551 | { | ||
552 | if (sclp_vt220_outqueue_count == 0) | ||
553 | sclp_vt220_emit_current(); | ||
554 | else | ||
555 | sclp_vt220_flush_later = 1; | ||
556 | } | ||
557 | |||
558 | /* | ||
559 | * This routine returns the numbers of characters the tty driver | ||
560 | * will accept for queuing to be written. This number is subject | ||
561 | * to change as output buffers get emptied, or if the output flow | ||
562 | * control is acted. | ||
563 | */ | ||
564 | static int | ||
565 | sclp_vt220_write_room(struct tty_struct *tty) | ||
566 | { | ||
567 | unsigned long flags; | ||
568 | struct list_head *l; | ||
569 | int count; | ||
570 | |||
571 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
572 | count = 0; | ||
573 | if (sclp_vt220_current_request != NULL) | ||
574 | count = sclp_vt220_space_left(sclp_vt220_current_request); | ||
575 | list_for_each(l, &sclp_vt220_empty) | ||
576 | count += SCLP_VT220_MAX_CHARS_PER_BUFFER; | ||
577 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
578 | return count; | ||
579 | } | ||
580 | |||
581 | /* | ||
582 | * Return number of buffered chars. | ||
583 | */ | ||
584 | static int | ||
585 | sclp_vt220_chars_in_buffer(struct tty_struct *tty) | ||
586 | { | ||
587 | unsigned long flags; | ||
588 | struct list_head *l; | ||
589 | struct sclp_vt220_request *r; | ||
590 | int count; | ||
591 | |||
592 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
593 | count = 0; | ||
594 | if (sclp_vt220_current_request != NULL) | ||
595 | count = sclp_vt220_chars_stored(sclp_vt220_current_request); | ||
596 | list_for_each(l, &sclp_vt220_outqueue) { | ||
597 | r = list_entry(l, struct sclp_vt220_request, list); | ||
598 | count += sclp_vt220_chars_stored(r); | ||
599 | } | ||
600 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
601 | return count; | ||
602 | } | ||
603 | |||
604 | static void | ||
605 | __sclp_vt220_flush_buffer(void) | ||
606 | { | ||
607 | unsigned long flags; | ||
608 | |||
609 | sclp_vt220_emit_current(); | ||
610 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
611 | if (timer_pending(&sclp_vt220_timer)) | ||
612 | del_timer(&sclp_vt220_timer); | ||
613 | while (sclp_vt220_outqueue_count > 0) { | ||
614 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
615 | sclp_sync_wait(); | ||
616 | spin_lock_irqsave(&sclp_vt220_lock, flags); | ||
617 | } | ||
618 | spin_unlock_irqrestore(&sclp_vt220_lock, flags); | ||
619 | } | ||
620 | |||
621 | /* | ||
622 | * Pass on all buffers to the hardware. Return only when there are no more | ||
623 | * buffers pending. | ||
624 | */ | ||
625 | static void | ||
626 | sclp_vt220_flush_buffer(struct tty_struct *tty) | ||
627 | { | ||
628 | sclp_vt220_emit_current(); | ||
629 | } | ||
630 | |||
631 | /* | ||
632 | * Initialize all relevant components and register driver with system. | ||
633 | */ | ||
634 | static int | ||
635 | __sclp_vt220_init(int early) | ||
636 | { | ||
637 | void *page; | ||
638 | int i; | ||
639 | |||
640 | if (sclp_vt220_initialized) | ||
641 | return 0; | ||
642 | sclp_vt220_initialized = 1; | ||
643 | spin_lock_init(&sclp_vt220_lock); | ||
644 | INIT_LIST_HEAD(&sclp_vt220_empty); | ||
645 | INIT_LIST_HEAD(&sclp_vt220_outqueue); | ||
646 | init_waitqueue_head(&sclp_vt220_waitq); | ||
647 | init_timer(&sclp_vt220_timer); | ||
648 | sclp_vt220_current_request = NULL; | ||
649 | sclp_vt220_buffered_chars = 0; | ||
650 | sclp_vt220_outqueue_count = 0; | ||
651 | sclp_vt220_tty = NULL; | ||
652 | sclp_vt220_flush_later = 0; | ||
653 | |||
654 | /* Allocate pages for output buffering */ | ||
655 | for (i = 0; i < (early ? MAX_CONSOLE_PAGES : MAX_KMEM_PAGES); i++) { | ||
656 | if (early) | ||
657 | page = alloc_bootmem_low_pages(PAGE_SIZE); | ||
658 | else | ||
659 | page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); | ||
660 | if (!page) | ||
661 | return -ENOMEM; | ||
662 | list_add_tail((struct list_head *) page, &sclp_vt220_empty); | ||
663 | } | ||
664 | return 0; | ||
665 | } | ||
666 | |||
667 | static struct tty_operations sclp_vt220_ops = { | ||
668 | .open = sclp_vt220_open, | ||
669 | .close = sclp_vt220_close, | ||
670 | .write = sclp_vt220_write, | ||
671 | .put_char = sclp_vt220_put_char, | ||
672 | .flush_chars = sclp_vt220_flush_chars, | ||
673 | .write_room = sclp_vt220_write_room, | ||
674 | .chars_in_buffer = sclp_vt220_chars_in_buffer, | ||
675 | .flush_buffer = sclp_vt220_flush_buffer | ||
676 | }; | ||
677 | |||
678 | /* | ||
679 | * Register driver with SCLP and Linux and initialize internal tty structures. | ||
680 | */ | ||
681 | int __init | ||
682 | sclp_vt220_tty_init(void) | ||
683 | { | ||
684 | struct tty_driver *driver; | ||
685 | int rc; | ||
686 | |||
687 | /* Note: we're not testing for CONSOLE_IS_SCLP here to preserve | ||
688 | * symmetry between VM and LPAR systems regarding ttyS1. */ | ||
689 | driver = alloc_tty_driver(1); | ||
690 | if (!driver) | ||
691 | return -ENOMEM; | ||
692 | rc = __sclp_vt220_init(0); | ||
693 | if (rc) { | ||
694 | put_tty_driver(driver); | ||
695 | return rc; | ||
696 | } | ||
697 | rc = sclp_register(&sclp_vt220_register); | ||
698 | if (rc) { | ||
699 | printk(KERN_ERR SCLP_VT220_PRINT_HEADER | ||
700 | "could not register tty - " | ||
701 | "sclp_register returned %d\n", rc); | ||
702 | put_tty_driver(driver); | ||
703 | return rc; | ||
704 | } | ||
705 | |||
706 | driver->owner = THIS_MODULE; | ||
707 | driver->driver_name = SCLP_VT220_DRIVER_NAME; | ||
708 | driver->name = SCLP_VT220_DEVICE_NAME; | ||
709 | driver->major = SCLP_VT220_MAJOR; | ||
710 | driver->minor_start = SCLP_VT220_MINOR; | ||
711 | driver->type = TTY_DRIVER_TYPE_SYSTEM; | ||
712 | driver->subtype = SYSTEM_TYPE_TTY; | ||
713 | driver->init_termios = tty_std_termios; | ||
714 | driver->flags = TTY_DRIVER_REAL_RAW; | ||
715 | tty_set_operations(driver, &sclp_vt220_ops); | ||
716 | |||
717 | rc = tty_register_driver(driver); | ||
718 | if (rc) { | ||
719 | printk(KERN_ERR SCLP_VT220_PRINT_HEADER | ||
720 | "could not register tty - " | ||
721 | "tty_register_driver returned %d\n", rc); | ||
722 | put_tty_driver(driver); | ||
723 | return rc; | ||
724 | } | ||
725 | sclp_vt220_driver = driver; | ||
726 | return 0; | ||
727 | } | ||
728 | |||
729 | module_init(sclp_vt220_tty_init); | ||
730 | |||
731 | #ifdef CONFIG_SCLP_VT220_CONSOLE | ||
732 | |||
733 | static void | ||
734 | sclp_vt220_con_write(struct console *con, const char *buf, unsigned int count) | ||
735 | { | ||
736 | __sclp_vt220_write((const unsigned char *) buf, count, 1, 1); | ||
737 | } | ||
738 | |||
739 | static struct tty_driver * | ||
740 | sclp_vt220_con_device(struct console *c, int *index) | ||
741 | { | ||
742 | *index = 0; | ||
743 | return sclp_vt220_driver; | ||
744 | } | ||
745 | |||
746 | /* | ||
747 | * This routine is called from panic when the kernel is going to give up. | ||
748 | * We have to make sure that all buffers will be flushed to the SCLP. | ||
749 | * Note that this function may be called from within an interrupt context. | ||
750 | */ | ||
751 | static void | ||
752 | sclp_vt220_con_unblank(void) | ||
753 | { | ||
754 | __sclp_vt220_flush_buffer(); | ||
755 | } | ||
756 | |||
757 | /* Structure needed to register with printk */ | ||
758 | static struct console sclp_vt220_console = | ||
759 | { | ||
760 | .name = SCLP_VT220_CONSOLE_NAME, | ||
761 | .write = sclp_vt220_con_write, | ||
762 | .device = sclp_vt220_con_device, | ||
763 | .unblank = sclp_vt220_con_unblank, | ||
764 | .flags = CON_PRINTBUFFER, | ||
765 | .index = SCLP_VT220_CONSOLE_INDEX | ||
766 | }; | ||
767 | |||
768 | static int __init | ||
769 | sclp_vt220_con_init(void) | ||
770 | { | ||
771 | int rc; | ||
772 | |||
773 | if (!CONSOLE_IS_SCLP) | ||
774 | return 0; | ||
775 | rc = __sclp_vt220_init(1); | ||
776 | if (rc) | ||
777 | return rc; | ||
778 | /* Attach linux console */ | ||
779 | register_console(&sclp_vt220_console); | ||
780 | return 0; | ||
781 | } | ||
782 | |||
783 | console_initcall(sclp_vt220_con_init); | ||
784 | #endif /* CONFIG_SCLP_VT220_CONSOLE */ | ||
785 | |||