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