• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
dcc_poll_locked(void)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 
dcc_tty_open(struct tty_struct * tty,struct file * filp)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 
dcc_tty_close(struct tty_struct * tty,struct file * filp)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 
dcc_write(const unsigned char * buf_start,int count)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 
dcc_tty_write(struct tty_struct * tty,const unsigned char * buf,int count)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 
dcc_tty_write_room(struct tty_struct * tty)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 
dcc_tty_chars_in_buffer(struct tty_struct * tty)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 
dcc_tty_unthrottle(struct tty_struct * tty)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 
dcc_tty_timer_func(struct hrtimer * timer)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 
dcc_console_write(struct console * co,const char * b,unsigned count)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 
dcc_console_device(struct console * c,int * index)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 
dcc_console_setup(struct console * co,char * options)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 
dcc_tty_init(void)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 
dcc_tty_exit(void)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 
327