diff options
Diffstat (limited to 'drivers/char/dcc_tty.c')
-rw-r--r-- | drivers/char/dcc_tty.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/drivers/char/dcc_tty.c b/drivers/char/dcc_tty.c new file mode 100644 index 00000000000..a787accdcb1 --- /dev/null +++ b/drivers/char/dcc_tty.c | |||
@@ -0,0 +1,326 @@ | |||
1 | /* drivers/char/dcc_tty.c | ||
2 | * | ||
3 | * Copyright (C) 2007 Google, Inc. | ||
4 | * | ||
5 | * This software is licensed under the terms of the GNU General Public | ||
6 | * License version 2, as published by the Free Software Foundation, and | ||
7 | * may be copied, distributed, and modified under those terms. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/console.h> | ||
20 | #include <linux/hrtimer.h> | ||
21 | #include <linux/tty.h> | ||
22 | #include <linux/tty_driver.h> | ||
23 | #include <linux/tty_flip.h> | ||
24 | |||
25 | MODULE_DESCRIPTION("DCC TTY Driver"); | ||
26 | MODULE_LICENSE("GPL"); | ||
27 | MODULE_VERSION("1.0"); | ||
28 | |||
29 | static spinlock_t g_dcc_tty_lock = SPIN_LOCK_UNLOCKED; | ||
30 | static struct hrtimer g_dcc_timer; | ||
31 | static char g_dcc_buffer[16]; | ||
32 | static int g_dcc_buffer_head; | ||
33 | static int g_dcc_buffer_count; | ||
34 | static unsigned g_dcc_write_delay_usecs = 1; | ||
35 | static struct tty_driver *g_dcc_tty_driver; | ||
36 | static struct tty_struct *g_dcc_tty; | ||
37 | static int g_dcc_tty_open_count; | ||
38 | |||
39 | static void dcc_poll_locked(void) | ||
40 | { | ||
41 | char ch; | ||
42 | int rch; | ||
43 | int written; | ||
44 | |||
45 | while (g_dcc_buffer_count) { | ||
46 | ch = g_dcc_buffer[g_dcc_buffer_head]; | ||
47 | asm( | ||
48 | "mrc 14, 0, r15, c0, c1, 0\n" | ||
49 | "mcrcc 14, 0, %1, c0, c5, 0\n" | ||
50 | "movcc %0, #1\n" | ||
51 | "movcs %0, #0\n" | ||
52 | : "=r" (written) | ||
53 | : "r" (ch) | ||
54 | ); | ||
55 | if (written) { | ||
56 | if (ch == '\n') | ||
57 | g_dcc_buffer[g_dcc_buffer_head] = '\r'; | ||
58 | else { | ||
59 | g_dcc_buffer_head = (g_dcc_buffer_head + 1) % ARRAY_SIZE(g_dcc_buffer); | ||
60 | g_dcc_buffer_count--; | ||
61 | if (g_dcc_tty) | ||
62 | tty_wakeup(g_dcc_tty); | ||
63 | } | ||
64 | g_dcc_write_delay_usecs = 1; | ||
65 | } else { | ||
66 | if (g_dcc_write_delay_usecs > 0x100) | ||
67 | break; | ||
68 | g_dcc_write_delay_usecs <<= 1; | ||
69 | udelay(g_dcc_write_delay_usecs); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | if (g_dcc_tty && !test_bit(TTY_THROTTLED, &g_dcc_tty->flags)) { | ||
74 | asm( | ||
75 | "mrc 14, 0, %0, c0, c1, 0\n" | ||
76 | "tst %0, #(1 << 30)\n" | ||
77 | "moveq %0, #-1\n" | ||
78 | "mrcne 14, 0, %0, c0, c5, 0\n" | ||
79 | : "=r" (rch) | ||
80 | ); | ||
81 | if (rch >= 0) { | ||
82 | ch = rch; | ||
83 | tty_insert_flip_string(g_dcc_tty, &ch, 1); | ||
84 | tty_flip_buffer_push(g_dcc_tty); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | |||
89 | if (g_dcc_buffer_count) | ||
90 | hrtimer_start(&g_dcc_timer, ktime_set(0, g_dcc_write_delay_usecs * NSEC_PER_USEC), HRTIMER_MODE_REL); | ||
91 | else | ||
92 | hrtimer_start(&g_dcc_timer, ktime_set(0, 20 * NSEC_PER_MSEC), HRTIMER_MODE_REL); | ||
93 | } | ||
94 | |||
95 | static int dcc_tty_open(struct tty_struct * tty, struct file * filp) | ||
96 | { | ||
97 | int ret; | ||
98 | unsigned long irq_flags; | ||
99 | |||
100 | spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); | ||
101 | if (g_dcc_tty == NULL || g_dcc_tty == tty) { | ||
102 | g_dcc_tty = tty; | ||
103 | g_dcc_tty_open_count++; | ||
104 | ret = 0; | ||
105 | } else | ||
106 | ret = -EBUSY; | ||
107 | spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); | ||
108 | |||
109 | printk("dcc_tty_open, tty %p, f_flags %x, returned %d\n", tty, filp->f_flags, ret); | ||
110 | |||
111 | return ret; | ||
112 | } | ||
113 | |||
114 | static void dcc_tty_close(struct tty_struct * tty, struct file * filp) | ||
115 | { | ||
116 | printk("dcc_tty_close, tty %p, f_flags %x\n", tty, filp->f_flags); | ||
117 | if (g_dcc_tty == tty) { | ||
118 | if (--g_dcc_tty_open_count == 0) | ||
119 | g_dcc_tty = NULL; | ||
120 | } | ||
121 | } | ||
122 | |||
123 | static int dcc_write(const unsigned char *buf_start, int count) | ||
124 | { | ||
125 | const unsigned char *buf = buf_start; | ||
126 | unsigned long irq_flags; | ||
127 | int copy_len; | ||
128 | int space_left; | ||
129 | int tail; | ||
130 | |||
131 | if (count < 1) | ||
132 | return 0; | ||
133 | |||
134 | spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); | ||
135 | do { | ||
136 | tail = (g_dcc_buffer_head + g_dcc_buffer_count) % ARRAY_SIZE(g_dcc_buffer); | ||
137 | copy_len = ARRAY_SIZE(g_dcc_buffer) - tail; | ||
138 | space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; | ||
139 | if (copy_len > space_left) | ||
140 | copy_len = space_left; | ||
141 | if (copy_len > count) | ||
142 | copy_len = count; | ||
143 | memcpy(&g_dcc_buffer[tail], buf, copy_len); | ||
144 | g_dcc_buffer_count += copy_len; | ||
145 | buf += copy_len; | ||
146 | count -= copy_len; | ||
147 | if (copy_len < count && copy_len < space_left) { | ||
148 | space_left -= copy_len; | ||
149 | copy_len = count; | ||
150 | if (copy_len > space_left) { | ||
151 | copy_len = space_left; | ||
152 | } | ||
153 | memcpy(g_dcc_buffer, buf, copy_len); | ||
154 | buf += copy_len; | ||
155 | count -= copy_len; | ||
156 | g_dcc_buffer_count += copy_len; | ||
157 | } | ||
158 | dcc_poll_locked(); | ||
159 | space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; | ||
160 | } while(count && space_left); | ||
161 | spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); | ||
162 | return buf - buf_start; | ||
163 | } | ||
164 | |||
165 | static int dcc_tty_write(struct tty_struct * tty, const unsigned char *buf, int count) | ||
166 | { | ||
167 | int ret; | ||
168 | /* printk("dcc_tty_write %p, %d\n", buf, count); */ | ||
169 | ret = dcc_write(buf, count); | ||
170 | if (ret != count) | ||
171 | printk("dcc_tty_write %p, %d, returned %d\n", buf, count, ret); | ||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | static int dcc_tty_write_room(struct tty_struct *tty) | ||
176 | { | ||
177 | int space_left; | ||
178 | unsigned long irq_flags; | ||
179 | |||
180 | spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); | ||
181 | space_left = ARRAY_SIZE(g_dcc_buffer) - g_dcc_buffer_count; | ||
182 | spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); | ||
183 | return space_left; | ||
184 | } | ||
185 | |||
186 | static int dcc_tty_chars_in_buffer(struct tty_struct *tty) | ||
187 | { | ||
188 | int ret; | ||
189 | asm( | ||
190 | "mrc 14, 0, %0, c0, c1, 0\n" | ||
191 | "mov %0, %0, LSR #30\n" | ||
192 | "and %0, %0, #1\n" | ||
193 | : "=r" (ret) | ||
194 | ); | ||
195 | return ret; | ||
196 | } | ||
197 | |||
198 | static void dcc_tty_unthrottle(struct tty_struct * tty) | ||
199 | { | ||
200 | unsigned long irq_flags; | ||
201 | |||
202 | spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); | ||
203 | dcc_poll_locked(); | ||
204 | spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); | ||
205 | } | ||
206 | |||
207 | static enum hrtimer_restart dcc_tty_timer_func(struct hrtimer *timer) | ||
208 | { | ||
209 | unsigned long irq_flags; | ||
210 | |||
211 | spin_lock_irqsave(&g_dcc_tty_lock, irq_flags); | ||
212 | dcc_poll_locked(); | ||
213 | spin_unlock_irqrestore(&g_dcc_tty_lock, irq_flags); | ||
214 | return HRTIMER_NORESTART; | ||
215 | } | ||
216 | |||
217 | void dcc_console_write(struct console *co, const char *b, unsigned count) | ||
218 | { | ||
219 | #if 1 | ||
220 | dcc_write(b, count); | ||
221 | #else | ||
222 | /* blocking printk */ | ||
223 | while (count > 0) { | ||
224 | int written; | ||
225 | written = dcc_write(b, count); | ||
226 | if (written) { | ||
227 | b += written; | ||
228 | count -= written; | ||
229 | } | ||
230 | } | ||
231 | #endif | ||
232 | } | ||
233 | |||
234 | static struct tty_driver *dcc_console_device(struct console *c, int *index) | ||
235 | { | ||
236 | *index = 0; | ||
237 | return g_dcc_tty_driver; | ||
238 | } | ||
239 | |||
240 | static int __init dcc_console_setup(struct console *co, char *options) | ||
241 | { | ||
242 | if (co->index != 0) | ||
243 | return -ENODEV; | ||
244 | return 0; | ||
245 | } | ||
246 | |||
247 | |||
248 | static struct console dcc_console = | ||
249 | { | ||
250 | .name = "ttyDCC", | ||
251 | .write = dcc_console_write, | ||
252 | .device = dcc_console_device, | ||
253 | .setup = dcc_console_setup, | ||
254 | .flags = CON_PRINTBUFFER, | ||
255 | .index = -1, | ||
256 | }; | ||
257 | |||
258 | static struct tty_operations dcc_tty_ops = { | ||
259 | .open = dcc_tty_open, | ||
260 | .close = dcc_tty_close, | ||
261 | .write = dcc_tty_write, | ||
262 | .write_room = dcc_tty_write_room, | ||
263 | .chars_in_buffer = dcc_tty_chars_in_buffer, | ||
264 | .unthrottle = dcc_tty_unthrottle, | ||
265 | }; | ||
266 | |||
267 | static int __init dcc_tty_init(void) | ||
268 | { | ||
269 | int ret; | ||
270 | |||
271 | hrtimer_init(&g_dcc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | ||
272 | g_dcc_timer.function = dcc_tty_timer_func; | ||
273 | |||
274 | g_dcc_tty_driver = alloc_tty_driver(1); | ||
275 | if (!g_dcc_tty_driver) { | ||
276 | printk(KERN_ERR "dcc_tty_probe: alloc_tty_driver failed\n"); | ||
277 | ret = -ENOMEM; | ||
278 | goto err_alloc_tty_driver_failed; | ||
279 | } | ||
280 | g_dcc_tty_driver->owner = THIS_MODULE; | ||
281 | g_dcc_tty_driver->driver_name = "dcc"; | ||
282 | g_dcc_tty_driver->name = "ttyDCC"; | ||
283 | g_dcc_tty_driver->major = 0; // auto assign | ||
284 | g_dcc_tty_driver->minor_start = 0; | ||
285 | g_dcc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; | ||
286 | g_dcc_tty_driver->subtype = SERIAL_TYPE_NORMAL; | ||
287 | g_dcc_tty_driver->init_termios = tty_std_termios; | ||
288 | g_dcc_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; | ||
289 | tty_set_operations(g_dcc_tty_driver, &dcc_tty_ops); | ||
290 | ret = tty_register_driver(g_dcc_tty_driver); | ||
291 | if (ret) { | ||
292 | printk(KERN_ERR "dcc_tty_probe: tty_register_driver failed, %d\n", ret); | ||
293 | goto err_tty_register_driver_failed; | ||
294 | } | ||
295 | tty_register_device(g_dcc_tty_driver, 0, NULL); | ||
296 | |||
297 | register_console(&dcc_console); | ||
298 | hrtimer_start(&g_dcc_timer, ktime_set(0, 0), HRTIMER_MODE_REL); | ||
299 | |||
300 | return 0; | ||
301 | |||
302 | err_tty_register_driver_failed: | ||
303 | put_tty_driver(g_dcc_tty_driver); | ||
304 | g_dcc_tty_driver = NULL; | ||
305 | err_alloc_tty_driver_failed: | ||
306 | return ret; | ||
307 | } | ||
308 | |||
309 | static void __exit dcc_tty_exit(void) | ||
310 | { | ||
311 | int ret; | ||
312 | |||
313 | tty_unregister_device(g_dcc_tty_driver, 0); | ||
314 | ret = tty_unregister_driver(g_dcc_tty_driver); | ||
315 | if (ret < 0) { | ||
316 | printk(KERN_ERR "dcc_tty_remove: tty_unregister_driver failed, %d\n", ret); | ||
317 | } else { | ||
318 | put_tty_driver(g_dcc_tty_driver); | ||
319 | } | ||
320 | g_dcc_tty_driver = NULL; | ||
321 | } | ||
322 | |||
323 | module_init(dcc_tty_init); | ||
324 | module_exit(dcc_tty_exit); | ||
325 | |||
326 | |||