blob: 377ad38460204a220ff04ef68433656f26bbb01c [file] [log] [blame]
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +08001/*
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +08002 * UART driver for the Greybus "generic" UART module.
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +08003 *
4 * Copyright 2014 Google Inc.
5 *
6 * Released under the GPLv2 only.
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +08007 *
8 * Heavily based on drivers/usb/class/cdc-acm.c and
9 * drivers/usb/serial/usb-serial.c.
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080010 */
11
12#include <linux/kernel.h>
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080013#include <linux/errno.h>
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080014#include <linux/module.h>
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080015#include <linux/sched.h>
16#include <linux/wait.h>
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080017#include <linux/slab.h>
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080018#include <linux/uaccess.h>
19#include <linux/mutex.h>
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080020#include <linux/tty.h>
21#include <linux/serial.h>
22#include <linux/tty_driver.h>
23#include <linux/tty_flip.h>
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080024#include <linux/serial.h>
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080025#include <linux/idr.h>
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080026#include "greybus.h"
27
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +080028#define GB_TTY_MAJOR 180 /* FIXME use a real number!!! */
29#define GB_NUM_MINORS 255 /* 255 is enough for anyone... */
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080030
31struct gb_tty {
32 struct tty_port port;
33 struct greybus_device *gdev;
34 int cport;
35 unsigned int minor;
36 unsigned char clocal;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +080037 unsigned int throttled:1;
38 unsigned int throttle_req:1;
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080039 bool disconnected;
40 int writesize; // FIXME - set this somehow.
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +080041 spinlock_t read_lock;
42 spinlock_t write_lock;
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080043 struct async_icount iocount;
44 struct async_icount oldcount;
45 wait_queue_head_t wioctl;
46 struct mutex mutex;
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +080047};
48
49static const struct greybus_device_id id_table[] = {
50 { GREYBUS_DEVICE(0x45, 0x45) }, /* make shit up */
51 { }, /* terminating NULL entry */
52};
53
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080054static struct tty_driver *gb_tty_driver;
55static DEFINE_IDR(tty_minors);
56static DEFINE_MUTEX(table_lock);
57
58static struct gb_tty *get_gb_by_minor(unsigned minor)
59{
60 struct gb_tty *gb_tty;
61
62 mutex_lock(&table_lock);
63 gb_tty = idr_find(&tty_minors, minor);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +080064 if (gb_tty) {
65 mutex_lock(&gb_tty->mutex);
66 if (gb_tty->disconnected) {
67 mutex_unlock(&gb_tty->mutex);
68 gb_tty = NULL;
69 } else {
70 tty_port_get(&gb_tty->port);
71 mutex_unlock(&gb_tty->mutex);
72 }
73 }
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080074 mutex_unlock(&table_lock);
75 return gb_tty;
76}
77
78static int alloc_minor(struct gb_tty *gb_tty)
79{
80 int minor;
81
82 mutex_lock(&table_lock);
Alex Elderff5f0b32014-08-18 18:25:11 -050083 minor = idr_alloc(&tty_minors, gb_tty, 0, GB_NUM_MINORS, GFP_KERNEL);
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080084 mutex_unlock(&table_lock);
Alex Elderff5f0b32014-08-18 18:25:11 -050085 if (minor >= 0)
86 gb_tty->minor = minor;
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080087 return minor;
88}
89
90static void release_minor(struct gb_tty *gb_tty)
91{
Alex Elderff5f0b32014-08-18 18:25:11 -050092 int minor = gb_tty->minor;
93
94 gb_tty->minor = 0; /* Maybe should use an invalid value instead */
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080095 mutex_lock(&table_lock);
Alex Elderff5f0b32014-08-18 18:25:11 -050096 idr_remove(&tty_minors, minor);
Greg Kroah-Hartmanff45c262014-08-15 18:33:33 +080097 mutex_unlock(&table_lock);
98}
99
100static int gb_tty_install(struct tty_driver *driver, struct tty_struct *tty)
101{
102 struct gb_tty *gb_tty;
103 int retval;
104
105 gb_tty = get_gb_by_minor(tty->index);
106 if (!gb_tty)
107 return -ENODEV;
108
109 retval = tty_standard_install(driver, tty);
110 if (retval)
111 goto error;
112
113 tty->driver_data = gb_tty;
114 return 0;
115error:
116 tty_port_put(&gb_tty->port);
117 return retval;
118}
119
120static int gb_tty_open(struct tty_struct *tty, struct file *file)
121{
122 struct gb_tty *gb_tty = tty->driver_data;
123
124 return tty_port_open(&gb_tty->port, tty, file);
125}
126
127static void gb_tty_close(struct tty_struct *tty, struct file *file)
128{
129 struct gb_tty *gb_tty = tty->driver_data;
130
131 tty_port_close(&gb_tty->port, tty, file);
132}
133
134static void gb_tty_cleanup(struct tty_struct *tty)
135{
136 struct gb_tty *gb_tty = tty->driver_data;
137
138 tty_port_put(&gb_tty->port);
139}
140
141static void gb_tty_hangup(struct tty_struct *tty)
142{
143 struct gb_tty *gb_tty = tty->driver_data;
144
145 tty_port_hangup(&gb_tty->port);
146}
147
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800148static int gb_tty_write(struct tty_struct *tty, const unsigned char *buf,
149 int count)
150{
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800151// struct gb_tty *gb_tty = tty->driver_data;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800152
153 // FIXME - actually implement...
154
155 return 0;
156}
157
158static int gb_tty_write_room(struct tty_struct *tty)
159{
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800160// struct gb_tty *gb_tty = tty->driver_data;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800161
162 // FIXME - how much do we want to say we have room for?
163 return 0;
164}
165
166static int gb_tty_chars_in_buffer(struct tty_struct *tty)
167{
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800168// struct gb_tty *gb_tty = tty->driver_data;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800169
170 // FIXME - how many left to send?
171 return 0;
172}
173
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800174static int gb_tty_break_ctl(struct tty_struct *tty, int state)
175{
176// struct gb_tty *gb_tty = tty->driver_data;
177
178 // FIXME - send a break, if asked to...
179 return 0;
180}
181
182static void gb_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
183{
184 // FIXME - is this it???
185 tty_termios_copy_hw(&tty->termios, old);
186}
187
188static int gb_tty_tiocmget(struct tty_struct *tty)
189{
190// struct gb_tty *gb_tty = tty->driver_data;
191
192 // FIXME - get some tiocms!
193 return 0;
194}
195
196static int gb_tty_tiocmset(struct tty_struct *tty, unsigned int set,
197 unsigned int clear)
198{
199// struct gb_tty *gb_tty = tty->driver_data;
200
201 // FIXME - set some tiocms!
202 return 0;
203}
204
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800205static void gb_tty_throttle(struct tty_struct *tty)
206{
207 struct gb_tty *gb_tty = tty->driver_data;
208
209 spin_lock_irq(&gb_tty->read_lock);
210 gb_tty->throttle_req = 1;
211 spin_unlock_irq(&gb_tty->read_lock);
212}
213
214static void gb_tty_unthrottle(struct tty_struct *tty)
215{
216 struct gb_tty *gb_tty = tty->driver_data;
217 unsigned int was_throttled;
218
219 spin_lock_irq(&gb_tty->read_lock);
220 was_throttled = gb_tty->throttled;
221 gb_tty->throttle_req = 0;
222 gb_tty->throttled = 0;
223 spin_unlock_irq(&gb_tty->read_lock);
224
225 if (was_throttled) {
226 // FIXME - send more data
227 }
228}
229
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800230static int get_serial_info(struct gb_tty *gb_tty,
231 struct serial_struct __user *info)
232{
233 struct serial_struct tmp;
234
235 if (!info)
236 return -EINVAL;
237
238 memset(&tmp, 0, sizeof(tmp));
239 tmp.flags = ASYNC_LOW_LATENCY;
240 tmp.xmit_fifo_size = gb_tty->writesize;
241 tmp.baud_base = 0; // FIXME
242 tmp.close_delay = gb_tty->port.close_delay / 10;
243 tmp.closing_wait = gb_tty->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
244 ASYNC_CLOSING_WAIT_NONE : gb_tty->port.closing_wait / 10;
245
246 if (copy_to_user(info, &tmp, sizeof(tmp)))
247 return -EFAULT;
248 else
249 return 0;
250}
251
252static int set_serial_info(struct gb_tty *gb_tty,
253 struct serial_struct __user *newinfo)
254{
255 struct serial_struct new_serial;
256 unsigned int closing_wait;
257 unsigned int close_delay;
258 int retval;
259
260 if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
261 return -EFAULT;
262
263 close_delay = new_serial.close_delay * 10;
264 closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
265 ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10;
266
267 mutex_lock(&gb_tty->port.mutex);
268 if (!capable(CAP_SYS_ADMIN)) {
269 if ((close_delay != gb_tty->port.close_delay) ||
270 (closing_wait != gb_tty->port.closing_wait))
271 retval = -EPERM;
272 else
273 retval = -EOPNOTSUPP;
274 } else {
275 gb_tty->port.close_delay = close_delay;
276 gb_tty->port.closing_wait = closing_wait;
277 }
278 mutex_unlock(&gb_tty->port.mutex);
279 return retval;
280}
281
282static int wait_serial_change(struct gb_tty *gb_tty, unsigned long arg)
283{
284 int retval = 0;
285 DECLARE_WAITQUEUE(wait, current);
286 struct async_icount old;
287 struct async_icount new;
288
Alex Eldercaaa8a82014-08-18 18:25:12 -0500289 if (!(arg & (TIOCM_DSR | TIOCM_RI | TIOCM_CD)))
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800290 return -EINVAL;
291
292 do {
293 spin_lock_irq(&gb_tty->read_lock);
294 old = gb_tty->oldcount;
295 new = gb_tty->iocount;
296 gb_tty->oldcount = new;
Alex Eldercaaa8a82014-08-18 18:25:12 -0500297 spin_unlock_irq(&gb_tty->read_lock);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800298
299 if ((arg & TIOCM_DSR) && (old.dsr != new.dsr))
300 break;
301 if ((arg & TIOCM_CD) && (old.dcd != new.dcd))
302 break;
303 if ((arg & TIOCM_RI) && (old.rng != new.rng))
304 break;
305
306 add_wait_queue(&gb_tty->wioctl, &wait);
307 set_current_state(TASK_INTERRUPTIBLE);
308 schedule();
309 remove_wait_queue(&gb_tty->wioctl, &wait);
310 if (gb_tty->disconnected) {
311 if (arg & TIOCM_CD)
312 break;
Alex Eldercaaa8a82014-08-18 18:25:12 -0500313 retval = -ENODEV;
314 } else if (signal_pending(current)) {
315 retval = -ERESTARTSYS;
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800316 }
317 } while (!retval);
318
319 return retval;
320}
321
322static int get_serial_usage(struct gb_tty *gb_tty,
323 struct serial_icounter_struct __user *count)
324{
325 struct serial_icounter_struct icount;
326 int retval = 0;
327
328 memset(&icount, 0, sizeof(icount));
329 icount.dsr = gb_tty->iocount.dsr;
330 icount.rng = gb_tty->iocount.rng;
331 icount.dcd = gb_tty->iocount.dcd;
332 icount.frame = gb_tty->iocount.frame;
333 icount.overrun = gb_tty->iocount.overrun;
334 icount.parity = gb_tty->iocount.parity;
335 icount.brk = gb_tty->iocount.brk;
336
337 if (copy_to_user(count, &icount, sizeof(icount)) > 0)
338 retval = -EFAULT;
339
340 return retval;
341}
342
343static int gb_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
344 unsigned long arg)
345{
346 struct gb_tty *gb_tty = tty->driver_data;
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800347
348 switch (cmd) {
349 case TIOCGSERIAL:
Greg Kroah-Hartman199d68d2014-08-30 16:20:22 -0700350 return get_serial_info(gb_tty,
351 (struct serial_struct __user *)arg);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800352 case TIOCSSERIAL:
Greg Kroah-Hartman199d68d2014-08-30 16:20:22 -0700353 return set_serial_info(gb_tty,
354 (struct serial_struct __user *)arg);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800355 case TIOCMIWAIT:
Greg Kroah-Hartman199d68d2014-08-30 16:20:22 -0700356 return wait_serial_change(gb_tty, arg);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800357 case TIOCGICOUNT:
Greg Kroah-Hartman199d68d2014-08-30 16:20:22 -0700358 return get_serial_usage(gb_tty,
359 (struct serial_icounter_struct __user *)arg);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800360 }
361
Greg Kroah-Hartman199d68d2014-08-30 16:20:22 -0700362 return -ENOIOCTLCMD;
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800363}
364
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800365
366static const struct tty_operations gb_ops = {
367 .install = gb_tty_install,
368 .open = gb_tty_open,
369 .close = gb_tty_close,
370 .cleanup = gb_tty_cleanup,
371 .hangup = gb_tty_hangup,
372 .write = gb_tty_write,
373 .write_room = gb_tty_write_room,
374 .ioctl = gb_tty_ioctl,
375 .throttle = gb_tty_throttle,
376 .unthrottle = gb_tty_unthrottle,
377 .chars_in_buffer = gb_tty_chars_in_buffer,
378 .break_ctl = gb_tty_break_ctl,
379 .set_termios = gb_tty_set_termios,
380 .tiocmget = gb_tty_tiocmget,
381 .tiocmset = gb_tty_tiocmset,
382};
383
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800384
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700385int gb_tty_probe(struct greybus_device *gdev,
386 const struct greybus_device_id *id)
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800387{
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800388 struct gb_tty *gb_tty;
389 struct device *tty_dev;
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800390 int retval;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800391 int minor;
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800392
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800393 gb_tty = devm_kzalloc(&gdev->dev, sizeof(*gb_tty), GFP_KERNEL);
394 if (!gb_tty)
395 return -ENOMEM;
396
397 minor = alloc_minor(gb_tty);
Alex Elderff5f0b32014-08-18 18:25:11 -0500398 if (minor < 0) {
399 if (minor == -ENOSPC) {
400 dev_err(&gdev->dev, "no more free minor numbers\n");
401 return -ENODEV;
402 }
403 return minor;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800404 }
405
406 gb_tty->minor = minor;
407 gb_tty->gdev = gdev;
408 spin_lock_init(&gb_tty->write_lock);
409 spin_lock_init(&gb_tty->read_lock);
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800410 init_waitqueue_head(&gb_tty->wioctl);
411 mutex_init(&gb_tty->mutex);
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800412
413 /* FIXME - allocate gb buffers */
414
Greg Kroah-Hartmaneca17c52014-08-30 16:54:05 -0700415 gdev->gb_tty = gb_tty;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800416
417 tty_dev = tty_port_register_device(&gb_tty->port, gb_tty_driver, minor,
418 &gdev->dev);
419 if (IS_ERR(tty_dev)) {
420 retval = PTR_ERR(tty_dev);
421 goto error;
422 }
423
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800424 return 0;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800425error:
Greg Kroah-Hartmaneca17c52014-08-30 16:54:05 -0700426 gdev->gb_tty = NULL;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800427 release_minor(gb_tty);
428 return retval;
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800429}
430
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700431void gb_tty_disconnect(struct greybus_device *gdev)
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800432{
Greg Kroah-Hartmaneca17c52014-08-30 16:54:05 -0700433 struct gb_tty *gb_tty = gdev->gb_tty;
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800434 struct tty_struct *tty;
435
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800436 if (!gb_tty)
437 return;
438
439 mutex_lock(&gb_tty->mutex);
440 gb_tty->disconnected = true;
441
442 wake_up_all(&gb_tty->wioctl);
Greg Kroah-Hartmaneca17c52014-08-30 16:54:05 -0700443 gdev->gb_tty = NULL;
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800444 mutex_unlock(&gb_tty->mutex);
445
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800446 tty = tty_port_tty_get(&gb_tty->port);
447 if (tty) {
448 tty_vhangup(tty);
449 tty_kref_put(tty);
450 }
451 /* FIXME - stop all traffic */
452
453 tty_unregister_device(gb_tty_driver, gb_tty->minor);
454
Greg Kroah-Hartmane68453e2014-08-15 19:44:32 +0800455 /* FIXME - free transmit / recieve buffers */
456
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800457 tty_port_put(&gb_tty->port);
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800458}
459
460static struct greybus_driver tty_gb_driver = {
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700461 .probe = gb_tty_probe,
462 .disconnect = gb_tty_disconnect,
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800463 .id_table = id_table,
464};
465
466
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700467int __init gb_tty_init(void)
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800468{
469 int retval;
470
Greg Kroah-Hartmana18e1512014-08-15 18:54:11 +0800471 gb_tty_driver = alloc_tty_driver(GB_NUM_MINORS);
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800472 if (!gb_tty_driver)
473 return -ENOMEM;
474
475 gb_tty_driver->driver_name = "gb";
476 gb_tty_driver->name = "ttyGB";
477 gb_tty_driver->major = GB_TTY_MAJOR;
478 gb_tty_driver->minor_start = 0;
479 gb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
480 gb_tty_driver->subtype = SERIAL_TYPE_NORMAL;
481 gb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
482 gb_tty_driver->init_termios = tty_std_termios;
483 gb_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
484 tty_set_operations(gb_tty_driver, &gb_ops);
485
486 retval = tty_register_driver(gb_tty_driver);
487 if (retval) {
488 put_tty_driver(gb_tty_driver);
489 return retval;
490 }
491
492 retval = greybus_register(&tty_gb_driver);
493 if (retval) {
494 tty_unregister_driver(gb_tty_driver);
495 put_tty_driver(gb_tty_driver);
496 }
497 return retval;
498}
499
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700500void __exit gb_tty_exit(void)
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800501{
502 greybus_deregister(&tty_gb_driver);
503 tty_unregister_driver(gb_tty_driver);
504 put_tty_driver(gb_tty_driver);
505}
506
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700507#if 0
Greg Kroah-Hartman79c822b2014-08-15 16:01:23 +0800508module_init(gb_tty_init);
509module_exit(gb_tty_exit);
510MODULE_LICENSE("GPL");
511MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");
Greg Kroah-Hartmandb6e1fd2014-08-30 16:47:26 -0700512#endif