aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/tty/goldfish.c
diff options
context:
space:
mode:
authorArve Hjønnevåg <arve@google.com>2013-01-21 18:38:47 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-01-21 18:30:08 -0500
commit666b7793d4bfa9f150b5c2007ab48c755ddc53ca (patch)
tree2056762794e853509294ff4e36291e2503cd6814 /drivers/tty/goldfish.c
parent4bbed6bc41aa76183449ade053891e28dec0ae3b (diff)
goldfish: tty driver
This provides a console driver for the Goldfish virtual platform. The original is from Arve with changes from Jun Nakajima and Tom Keel. This has been then been ported to the current kernel and to the tty port mechanism by Alan Cox. In the process it gained proper POSIX semantics and vhangup works. The default name is not ttyS as this belongs to the 8250 driver. Instead ttyGFx is now used. In the normal usage case the first port serves as a kernel logging console and the second one carries various other data streams for the emulation. Signed-off-by: Arve Hjønnevåg <arve@google.com> [Cleaned up to handle x86] Signed-off-by: Sheng Yang <sheng@linux.intel.com> Signed-off-by: Yunhong Jiang <yunhong.jiang@intel.com> Signed-off-by: Xiaohui Xin <xiaohui.xin@intel.com> Signed-off-by: Jun Nakajima <jun.nakajima@intel.com> Signed-off-by: Bruce Beare <bruce.j.beare@intel.com> [Moved to 3.7 and chunks rewritten to use tty_port layer] Signed-off-by: Alan Cox <alan@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty/goldfish.c')
-rw-r--r--drivers/tty/goldfish.c333
1 files changed, 333 insertions, 0 deletions
diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c
new file mode 100644
index 000000000000..e2ccb6daa6c5
--- /dev/null
+++ b/drivers/tty/goldfish.c
@@ -0,0 +1,333 @@
1/*
2 * Copyright (C) 2007 Google, Inc.
3 * Copyright (C) 2012 Intel, 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/console.h>
17#include <linux/init.h>
18#include <linux/interrupt.h>
19#include <linux/platform_device.h>
20#include <linux/tty.h>
21#include <linux/tty_flip.h>
22#include <linux/slab.h>
23#include <linux/io.h>
24#include <linux/module.h>
25
26enum {
27 GOLDFISH_TTY_PUT_CHAR = 0x00,
28 GOLDFISH_TTY_BYTES_READY = 0x04,
29 GOLDFISH_TTY_CMD = 0x08,
30
31 GOLDFISH_TTY_DATA_PTR = 0x10,
32 GOLDFISH_TTY_DATA_LEN = 0x14,
33
34 GOLDFISH_TTY_CMD_INT_DISABLE = 0,
35 GOLDFISH_TTY_CMD_INT_ENABLE = 1,
36 GOLDFISH_TTY_CMD_WRITE_BUFFER = 2,
37 GOLDFISH_TTY_CMD_READ_BUFFER = 3,
38};
39
40struct goldfish_tty {
41 struct tty_port port;
42 spinlock_t lock;
43 void __iomem *base;
44 u32 irq;
45 int opencount;
46 struct console console;
47};
48
49static DEFINE_MUTEX(goldfish_tty_lock);
50static struct tty_driver *goldfish_tty_driver;
51static u32 goldfish_tty_line_count = 8;
52static u32 goldfish_tty_current_line_count;
53static struct goldfish_tty *goldfish_ttys;
54
55static void goldfish_tty_do_write(int line, const char *buf, unsigned count)
56{
57 unsigned long irq_flags;
58 struct goldfish_tty *qtty = &goldfish_ttys[line];
59 void __iomem *base = qtty->base;
60 spin_lock_irqsave(&qtty->lock, irq_flags);
61 writel((u32)buf, base + GOLDFISH_TTY_DATA_PTR);
62 writel(count, base + GOLDFISH_TTY_DATA_LEN);
63 writel(GOLDFISH_TTY_CMD_WRITE_BUFFER, base + GOLDFISH_TTY_CMD);
64 spin_unlock_irqrestore(&qtty->lock, irq_flags);
65}
66
67static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
68{
69 struct platform_device *pdev = dev_id;
70 struct goldfish_tty *qtty = &goldfish_ttys[pdev->id];
71 void __iomem *base = qtty->base;
72 unsigned long irq_flags;
73 unsigned char *buf;
74 u32 count;
75 struct tty_struct *tty;
76
77 count = readl(base + GOLDFISH_TTY_BYTES_READY);
78 if(count == 0)
79 return IRQ_NONE;
80
81 tty = tty_port_tty_get(&qtty->port);
82 if (tty) {
83 count = tty_prepare_flip_string(tty, &buf, count);
84 spin_lock_irqsave(&qtty->lock, irq_flags);
85 writel((u32)buf, base + GOLDFISH_TTY_DATA_PTR);
86 writel(count, base + GOLDFISH_TTY_DATA_LEN);
87 writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_CMD);
88 spin_unlock_irqrestore(&qtty->lock, irq_flags);
89 tty_schedule_flip(tty);
90 tty_kref_put(tty);
91 }
92 return IRQ_HANDLED;
93}
94
95static int goldfish_tty_activate(struct tty_port *port, struct tty_struct *tty)
96{
97 struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, port);
98 writel(GOLDFISH_TTY_CMD_INT_ENABLE, qtty->base + GOLDFISH_TTY_CMD);
99 return 0;
100}
101
102static void goldfish_tty_shutdown(struct tty_port *port)
103{
104 struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, port);
105 writel(GOLDFISH_TTY_CMD_INT_DISABLE, qtty->base + GOLDFISH_TTY_CMD);
106}
107
108static int goldfish_tty_open(struct tty_struct * tty, struct file * filp)
109{
110 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
111 return tty_port_open(&qtty->port, tty, filp);
112}
113
114static void goldfish_tty_close(struct tty_struct * tty, struct file * filp)
115{
116 tty_port_close(tty->port, tty, filp);
117}
118
119static void goldfish_tty_hangup(struct tty_struct *tty)
120{
121 tty_port_hangup(tty->port);
122}
123
124static int goldfish_tty_write(struct tty_struct * tty, const unsigned char *buf, int count)
125{
126 goldfish_tty_do_write(tty->index, buf, count);
127 return count;
128}
129
130static int goldfish_tty_write_room(struct tty_struct *tty)
131{
132 return 0x10000;
133}
134
135static int goldfish_tty_chars_in_buffer(struct tty_struct *tty)
136{
137 struct goldfish_tty *qtty = &goldfish_ttys[tty->index];
138 void __iomem *base = qtty->base;
139 return readl(base + GOLDFISH_TTY_BYTES_READY);
140}
141
142static void goldfish_tty_console_write(struct console *co, const char *b, unsigned count)
143{
144 goldfish_tty_do_write(co->index, b, count);
145}
146
147static struct tty_driver *goldfish_tty_console_device(struct console *c, int *index)
148{
149 *index = c->index;
150 return goldfish_tty_driver;
151}
152
153static int goldfish_tty_console_setup(struct console *co, char *options)
154{
155 if((unsigned)co->index > goldfish_tty_line_count)
156 return -ENODEV;
157 if(goldfish_ttys[co->index].base == 0)
158 return -ENODEV;
159 return 0;
160}
161
162static struct tty_port_operations goldfish_port_ops = {
163 .activate = goldfish_tty_activate,
164 .shutdown = goldfish_tty_shutdown
165};
166
167static struct tty_operations goldfish_tty_ops = {
168 .open = goldfish_tty_open,
169 .close = goldfish_tty_close,
170 .hangup = goldfish_tty_hangup,
171 .write = goldfish_tty_write,
172 .write_room = goldfish_tty_write_room,
173 .chars_in_buffer = goldfish_tty_chars_in_buffer,
174};
175
176static int goldfish_tty_create_driver(void)
177{
178 int ret;
179 struct tty_driver *tty;
180
181 goldfish_ttys = kzalloc(sizeof(*goldfish_ttys) * goldfish_tty_line_count, GFP_KERNEL);
182 if(goldfish_ttys == NULL) {
183 ret = -ENOMEM;
184 goto err_alloc_goldfish_ttys_failed;
185 }
186 tty = alloc_tty_driver(goldfish_tty_line_count);
187 if(tty == NULL) {
188 ret = -ENOMEM;
189 goto err_alloc_tty_driver_failed;
190 }
191 tty->driver_name = "goldfish";
192 tty->name = "ttyGF";
193 tty->type = TTY_DRIVER_TYPE_SERIAL;
194 tty->subtype = SERIAL_TYPE_NORMAL;
195 tty->init_termios = tty_std_termios;
196 tty->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
197 tty_set_operations(tty, &goldfish_tty_ops);
198 ret = tty_register_driver(tty);
199 if(ret)
200 goto err_tty_register_driver_failed;
201
202 goldfish_tty_driver = tty;
203 return 0;
204
205err_tty_register_driver_failed:
206 put_tty_driver(tty);
207err_alloc_tty_driver_failed:
208 kfree(goldfish_ttys);
209 goldfish_ttys = NULL;
210err_alloc_goldfish_ttys_failed:
211 return ret;
212}
213
214static void goldfish_tty_delete_driver(void)
215{
216 tty_unregister_driver(goldfish_tty_driver);
217 put_tty_driver(goldfish_tty_driver);
218 goldfish_tty_driver = NULL;
219 kfree(goldfish_ttys);
220 goldfish_ttys = NULL;
221}
222
223static int goldfish_tty_probe(struct platform_device *pdev)
224{
225 struct goldfish_tty *qtty;
226 int ret = -EINVAL;
227 int i;
228 struct resource *r;
229 struct device *ttydev;
230 void __iomem *base;
231 u32 irq;
232
233 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
234 if(r == NULL)
235 return -EINVAL;
236
237 base = ioremap(r->start, 0x1000);
238 if (base == NULL)
239 pr_err("goldfish_tty: unable to remap base\n");
240
241 r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
242 if(r == NULL)
243 goto err_unmap;
244
245 irq = r->start;
246
247 if(pdev->id >= goldfish_tty_line_count)
248 goto err_unmap;
249
250 mutex_lock(&goldfish_tty_lock);
251 if(goldfish_tty_current_line_count == 0) {
252 ret = goldfish_tty_create_driver();
253 if(ret)
254 goto err_create_driver_failed;
255 }
256 goldfish_tty_current_line_count++;
257
258 qtty = &goldfish_ttys[pdev->id];
259 spin_lock_init(&qtty->lock);
260 tty_port_init(&qtty->port);
261 qtty->port.ops = &goldfish_port_ops;
262 qtty->base = base;
263 qtty->irq = irq;
264
265 writel(GOLDFISH_TTY_CMD_INT_DISABLE, base + GOLDFISH_TTY_CMD);
266
267 ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED, "goldfish_tty", pdev);
268 if(ret)
269 goto err_request_irq_failed;
270
271
272 ttydev = tty_port_register_device(&qtty->port, goldfish_tty_driver,
273 pdev->id, &pdev->dev);
274 if(IS_ERR(ttydev)) {
275 ret = PTR_ERR(ttydev);
276 goto err_tty_register_device_failed;
277 }
278
279 strcpy(qtty->console.name, "ttyGF");
280 qtty->console.write = goldfish_tty_console_write;
281 qtty->console.device = goldfish_tty_console_device;
282 qtty->console.setup = goldfish_tty_console_setup;
283 qtty->console.flags = CON_PRINTBUFFER;
284 qtty->console.index = pdev->id;
285 register_console(&qtty->console);
286
287 mutex_unlock(&goldfish_tty_lock);
288 return 0;
289
290 tty_unregister_device(goldfish_tty_driver, i);
291err_tty_register_device_failed:
292 free_irq(irq, pdev);
293err_request_irq_failed:
294 goldfish_tty_current_line_count--;
295 if(goldfish_tty_current_line_count == 0)
296 goldfish_tty_delete_driver();
297err_create_driver_failed:
298 mutex_unlock(&goldfish_tty_lock);
299err_unmap:
300 iounmap(base);
301 return ret;
302}
303
304static int goldfish_tty_remove(struct platform_device *pdev)
305{
306 struct goldfish_tty *qtty;
307
308 mutex_lock(&goldfish_tty_lock);
309
310 qtty = &goldfish_ttys[pdev->id];
311 unregister_console(&qtty->console);
312 tty_unregister_device(goldfish_tty_driver, pdev->id);
313 iounmap(qtty->base);
314 qtty->base = 0;
315 free_irq(qtty->irq, pdev);
316 goldfish_tty_current_line_count--;
317 if(goldfish_tty_current_line_count == 0)
318 goldfish_tty_delete_driver();
319 mutex_unlock(&goldfish_tty_lock);
320 return 0;
321}
322
323static struct platform_driver goldfish_tty_platform_driver = {
324 .probe = goldfish_tty_probe,
325 .remove = goldfish_tty_remove,
326 .driver = {
327 .name = "goldfish_tty"
328 }
329};
330
331module_platform_driver(goldfish_tty_platform_driver);
332
333MODULE_LICENSE("GPL v2");