Commit | Line | Data |
---|---|---|
6fa3eb70 S |
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 |